Compare commits
3 Commits
db-postgre
...
v0.0.23
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9aaadfa115 | ||
|
|
fe25c1920a | ||
|
|
0f1ba6b315 |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@payloadcms/payload",
|
"name": "@payloadcms/payload",
|
||||||
"version": "0.0.22",
|
"version": "0.0.23",
|
||||||
"description": "CMS and Application Framework",
|
"description": "CMS and Application Framework",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"author": "Payload CMS LLC",
|
"author": "Payload CMS LLC",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import config from 'payload/config';
|
import config from 'payload/config';
|
||||||
import Button from '../Button';
|
import Button from '../Button';
|
||||||
import { useForm } from '../../forms/Form/context';
|
import { useForm } from '../../forms/FormProvider/context';
|
||||||
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useForm } from '../../forms/Form/context';
|
import { useForm } from '../../forms/FormProvider/context';
|
||||||
import { useUser } from '../../data/User';
|
import { useUser } from '../../data/User';
|
||||||
import Button from '../Button';
|
import Button from '../Button';
|
||||||
|
|
||||||
|
|||||||
@@ -1,281 +1,21 @@
|
|||||||
import React, {
|
import React from 'react';
|
||||||
useReducer, useEffect, useRef, useState,
|
|
||||||
} from 'react';
|
|
||||||
import { objectToFormData } from 'object-to-formdata';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { unflatten } from 'flatley';
|
import { useFormFields } from '../FormProvider/context';
|
||||||
import HiddenInput from '../field-types/HiddenInput';
|
import HiddenInput from '../field-types/HiddenInput';
|
||||||
import { useLocale } from '../../utilities/Locale';
|
import { useLocale } from '../../utilities/Locale';
|
||||||
import { useStatusList } from '../../elements/Status';
|
|
||||||
import { requests } from '../../../api';
|
|
||||||
import useThrottledEffect from '../../../hooks/useThrottledEffect';
|
|
||||||
import { useUser } from '../../data/User';
|
|
||||||
import fieldReducer from './fieldReducer';
|
|
||||||
import initContextState from './initContextState';
|
|
||||||
|
|
||||||
import { SubmittedContext, ProcessingContext, ModifiedContext, FormContext, FieldContext } from './context';
|
|
||||||
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const baseClass = 'form';
|
const baseClass = 'form';
|
||||||
|
|
||||||
const reduceFieldsToValues = (fields, flatten) => {
|
|
||||||
const data = {};
|
|
||||||
|
|
||||||
Object.keys(fields).forEach((key) => {
|
|
||||||
if (!fields[key].disableFormData && fields[key].value !== undefined) {
|
|
||||||
data[key] = fields[key].value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (flatten) {
|
|
||||||
return unflatten(data, { safe: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Form = (props) => {
|
const Form = (props) => {
|
||||||
const {
|
const {
|
||||||
disabled,
|
|
||||||
onSubmit,
|
|
||||||
ajax,
|
|
||||||
method,
|
|
||||||
action,
|
|
||||||
handleResponse,
|
|
||||||
onSuccess,
|
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
redirect,
|
|
||||||
disableSuccessStatus,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const history = useHistory();
|
|
||||||
const locale = useLocale();
|
const locale = useLocale();
|
||||||
const { replaceStatus, addStatus, clearStatus } = useStatusList();
|
const { submit } = useFormFields();
|
||||||
const { refreshCookie } = useUser();
|
|
||||||
|
|
||||||
const [modified, setModified] = useState(false);
|
|
||||||
const [processing, setProcessing] = useState(false);
|
|
||||||
const [submitted, setSubmitted] = useState(false);
|
|
||||||
|
|
||||||
const contextRef = useRef({ ...initContextState });
|
|
||||||
|
|
||||||
const [fields, dispatchFields] = useReducer(fieldReducer, {});
|
|
||||||
contextRef.current.fields = fields;
|
|
||||||
contextRef.current.dispatchFields = dispatchFields;
|
|
||||||
|
|
||||||
contextRef.current.submit = (e) => {
|
|
||||||
if (disabled) {
|
|
||||||
e.preventDefault();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
e.stopPropagation();
|
|
||||||
setSubmitted(true);
|
|
||||||
|
|
||||||
const isValid = contextRef.current.validateForm();
|
|
||||||
|
|
||||||
// If not valid, prevent submission
|
|
||||||
if (!isValid) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
addStatus({
|
|
||||||
message: 'Please correct the fields below.',
|
|
||||||
type: 'error',
|
|
||||||
});
|
|
||||||
|
|
||||||
window.scrollTo({
|
|
||||||
top: 0,
|
|
||||||
behavior: 'smooth',
|
|
||||||
});
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If submit handler comes through via props, run that
|
|
||||||
if (onSubmit) {
|
|
||||||
e.preventDefault();
|
|
||||||
return onSubmit(fields);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If form is AJAX, fetch data
|
|
||||||
if (ajax !== false) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
window.scrollTo({
|
|
||||||
top: 0,
|
|
||||||
behavior: 'smooth',
|
|
||||||
});
|
|
||||||
|
|
||||||
const formData = contextRef.current.createFormData();
|
|
||||||
setProcessing(true);
|
|
||||||
|
|
||||||
// Make the API call from the action
|
|
||||||
return requests[method.toLowerCase()](action, {
|
|
||||||
body: formData,
|
|
||||||
}).then((res) => {
|
|
||||||
setModified(false);
|
|
||||||
if (typeof handleResponse === 'function') return handleResponse(res);
|
|
||||||
|
|
||||||
return res.json().then((json) => {
|
|
||||||
setProcessing(false);
|
|
||||||
clearStatus();
|
|
||||||
|
|
||||||
if (res.status < 400) {
|
|
||||||
if (typeof onSuccess === 'function') onSuccess(json);
|
|
||||||
|
|
||||||
if (redirect) {
|
|
||||||
const destination = {
|
|
||||||
pathname: redirect,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (json.message && !disableSuccessStatus) {
|
|
||||||
destination.state = {
|
|
||||||
status: [
|
|
||||||
{
|
|
||||||
message: json.message,
|
|
||||||
type: 'success',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
history.push(destination);
|
|
||||||
} else if (!disableSuccessStatus) {
|
|
||||||
replaceStatus([{
|
|
||||||
message: json.message,
|
|
||||||
type: 'success',
|
|
||||||
disappear: 3000,
|
|
||||||
}]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (json.message) {
|
|
||||||
addStatus({
|
|
||||||
message: json.message,
|
|
||||||
type: 'error',
|
|
||||||
});
|
|
||||||
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(json.errors)) {
|
|
||||||
const [fieldErrors, nonFieldErrors] = json.errors.reduce(([fieldErrs, nonFieldErrs], err) => (err.field && err.message ? [[...fieldErrs, err], nonFieldErrs] : [fieldErrs, [...nonFieldErrs, err]]), [[], []]);
|
|
||||||
|
|
||||||
fieldErrors.forEach((err) => {
|
|
||||||
dispatchFields({
|
|
||||||
valid: false,
|
|
||||||
errorMessage: err.message,
|
|
||||||
path: err.field,
|
|
||||||
value: contextRef.current.fields?.[err.field]?.value,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
nonFieldErrors.forEach((err) => {
|
|
||||||
addStatus({
|
|
||||||
message: err.message || 'An unknown error occurred.',
|
|
||||||
type: 'error',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (fieldErrors.length > 0 && nonFieldErrors.length === 0) {
|
|
||||||
addStatus({
|
|
||||||
message: 'Please correct the fields below.',
|
|
||||||
type: 'error',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
addStatus({
|
|
||||||
message: 'An unknown error occurred.',
|
|
||||||
type: 'error',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return json;
|
|
||||||
});
|
|
||||||
}).catch((err) => {
|
|
||||||
addStatus({
|
|
||||||
message: err,
|
|
||||||
type: 'error',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
contextRef.current.getFields = () => contextRef.current.fields;
|
|
||||||
|
|
||||||
contextRef.current.getField = (path) => contextRef.current.fields[path];
|
|
||||||
|
|
||||||
contextRef.current.getData = () => reduceFieldsToValues(contextRef.current.fields, true);
|
|
||||||
|
|
||||||
contextRef.current.getSiblingData = (path) => {
|
|
||||||
let siblingFields = contextRef.current.fields;
|
|
||||||
|
|
||||||
// If this field is nested
|
|
||||||
// We can provide a list of sibling fields
|
|
||||||
if (path.indexOf('.') > 0) {
|
|
||||||
const parentFieldPath = path.substring(0, path.lastIndexOf('.') + 1);
|
|
||||||
siblingFields = Object.keys(contextRef.current.fields).reduce((siblings, fieldKey) => {
|
|
||||||
if (fieldKey.indexOf(parentFieldPath) === 0) {
|
|
||||||
return {
|
|
||||||
...siblings,
|
|
||||||
[fieldKey.replace(parentFieldPath, '')]: contextRef.current.fields[fieldKey],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return siblings;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
return reduceFieldsToValues(siblingFields, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
contextRef.current.getDataByPath = (path) => {
|
|
||||||
const pathPrefixToRemove = path.substring(0, path.lastIndexOf('.') + 1);
|
|
||||||
const name = path.split('.').pop();
|
|
||||||
|
|
||||||
const data = Object.keys(contextRef.current.fields).reduce((matchedData, key) => {
|
|
||||||
if (key.indexOf(`${path}.`) === 0) {
|
|
||||||
return {
|
|
||||||
...matchedData,
|
|
||||||
[key.replace(pathPrefixToRemove, '')]: contextRef.current.fields[key],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchedData;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
const values = reduceFieldsToValues(data, true);
|
|
||||||
const unflattenedData = unflatten(values);
|
|
||||||
return unflattenedData?.[name];
|
|
||||||
};
|
|
||||||
|
|
||||||
contextRef.current.getUnflattenedValues = () => reduceFieldsToValues(contextRef.current.fields);
|
|
||||||
|
|
||||||
contextRef.current.validateForm = () => !Object.values(contextRef.current.fields).some((field) => field.valid === false);
|
|
||||||
|
|
||||||
contextRef.current.createFormData = () => {
|
|
||||||
const data = reduceFieldsToValues(contextRef.current.fields);
|
|
||||||
return objectToFormData(data, { indices: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
contextRef.current.setModified = setModified;
|
|
||||||
contextRef.current.setProcessing = setProcessing;
|
|
||||||
contextRef.current.setSubmitted = setSubmitted;
|
|
||||||
|
|
||||||
useThrottledEffect(() => {
|
|
||||||
refreshCookie();
|
|
||||||
}, 15000, [fields]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setModified(false);
|
|
||||||
}, [locale]);
|
|
||||||
|
|
||||||
const classes = [
|
const classes = [
|
||||||
className,
|
className,
|
||||||
@@ -285,63 +25,28 @@ const Form = (props) => {
|
|||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
noValidate
|
noValidate
|
||||||
onSubmit={contextRef.current.submit}
|
onSubmit={submit}
|
||||||
method={method}
|
|
||||||
action={action}
|
|
||||||
className={classes}
|
className={classes}
|
||||||
>
|
>
|
||||||
<FormContext.Provider value={contextRef.current}>
|
<HiddenInput
|
||||||
<FieldContext.Provider value={{
|
path="locale"
|
||||||
fields,
|
defaultValue={locale}
|
||||||
...contextRef.current,
|
/>
|
||||||
}}
|
{children}
|
||||||
>
|
|
||||||
<SubmittedContext.Provider value={submitted}>
|
|
||||||
<ProcessingContext.Provider value={processing}>
|
|
||||||
<ModifiedContext.Provider value={modified}>
|
|
||||||
<HiddenInput
|
|
||||||
path="locale"
|
|
||||||
defaultValue={locale}
|
|
||||||
/>
|
|
||||||
{children}
|
|
||||||
</ModifiedContext.Provider>
|
|
||||||
</ProcessingContext.Provider>
|
|
||||||
</SubmittedContext.Provider>
|
|
||||||
</FieldContext.Provider>
|
|
||||||
</FormContext.Provider>
|
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Form.defaultProps = {
|
Form.defaultProps = {
|
||||||
redirect: '',
|
|
||||||
onSubmit: null,
|
|
||||||
ajax: true,
|
|
||||||
method: 'POST',
|
|
||||||
action: '',
|
|
||||||
handleResponse: null,
|
|
||||||
onSuccess: null,
|
|
||||||
className: '',
|
className: '',
|
||||||
disableSuccessStatus: false,
|
|
||||||
disabled: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Form.propTypes = {
|
Form.propTypes = {
|
||||||
disableSuccessStatus: PropTypes.bool,
|
|
||||||
onSubmit: PropTypes.func,
|
|
||||||
ajax: PropTypes.bool,
|
|
||||||
method: PropTypes.oneOf(['post', 'POST', 'get', 'GET', 'put', 'PUT', 'delete', 'DELETE']),
|
|
||||||
action: PropTypes.string,
|
|
||||||
handleResponse: PropTypes.func,
|
|
||||||
onSuccess: PropTypes.func,
|
|
||||||
children: PropTypes.oneOfType([
|
children: PropTypes.oneOfType([
|
||||||
PropTypes.arrayOf(PropTypes.node),
|
PropTypes.arrayOf(PropTypes.node),
|
||||||
PropTypes.node,
|
PropTypes.node,
|
||||||
]).isRequired,
|
]).isRequired,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
redirect: PropTypes.string,
|
|
||||||
disabled: PropTypes.bool,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Form;
|
export default Form;
|
||||||
|
|||||||
@@ -5,6 +5,26 @@ function fieldReducer(state, action) {
|
|||||||
...action.value,
|
...action.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case 'REPLACE_ALL_BY_PATH': {
|
||||||
|
const { path, value = {} } = action;
|
||||||
|
|
||||||
|
const newState = Object.entries(state).reduce((reducedState, [key, val]) => {
|
||||||
|
if (key.indexOf(`${path}`) === 0) {
|
||||||
|
return reducedState;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...reducedState,
|
||||||
|
[key]: val,
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...newState,
|
||||||
|
...value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
case 'REMOVE': {
|
case 'REMOVE': {
|
||||||
const newState = { ...state };
|
const newState = { ...state };
|
||||||
delete newState[action.path];
|
delete newState[action.path];
|
||||||
315
src/client/components/forms/FormProvider/index.js
Normal file
315
src/client/components/forms/FormProvider/index.js
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
import React, {
|
||||||
|
useReducer, useEffect, useRef, useState,
|
||||||
|
} from 'react';
|
||||||
|
import { objectToFormData } from 'object-to-formdata';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { unflatten } from 'flatley';
|
||||||
|
import { useLocale } from '../../utilities/Locale';
|
||||||
|
import { useStatusList } from '../../elements/Status';
|
||||||
|
import { requests } from '../../../api';
|
||||||
|
import useThrottledEffect from '../../../hooks/useThrottledEffect';
|
||||||
|
import { useUser } from '../../data/User';
|
||||||
|
import fieldReducer from './fieldReducer';
|
||||||
|
import initContextState from './initContextState';
|
||||||
|
|
||||||
|
import { SubmittedContext, ProcessingContext, ModifiedContext, FormContext, FieldContext } from './context';
|
||||||
|
|
||||||
|
const reduceFieldsToValues = (fields, flatten) => {
|
||||||
|
const data = {};
|
||||||
|
|
||||||
|
Object.keys(fields).forEach((key) => {
|
||||||
|
if (!fields[key].disableFormData && fields[key].value !== undefined) {
|
||||||
|
data[key] = fields[key].value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (flatten) {
|
||||||
|
return unflatten(data, { safe: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const FormProvider = (props) => {
|
||||||
|
const {
|
||||||
|
disabled,
|
||||||
|
onSubmit,
|
||||||
|
method,
|
||||||
|
action,
|
||||||
|
handleResponse,
|
||||||
|
onSuccess,
|
||||||
|
children,
|
||||||
|
redirect,
|
||||||
|
disableSuccessStatus,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const history = useHistory();
|
||||||
|
const locale = useLocale();
|
||||||
|
const { replaceStatus, addStatus, clearStatus } = useStatusList();
|
||||||
|
const { refreshCookie } = useUser();
|
||||||
|
|
||||||
|
const [modified, setModified] = useState(false);
|
||||||
|
const [processing, setProcessing] = useState(false);
|
||||||
|
const [submitted, setSubmitted] = useState(false);
|
||||||
|
|
||||||
|
const contextRef = useRef({ ...initContextState });
|
||||||
|
|
||||||
|
const [fields, dispatchFields] = useReducer(fieldReducer, {});
|
||||||
|
contextRef.current.fields = fields;
|
||||||
|
contextRef.current.dispatchFields = dispatchFields;
|
||||||
|
|
||||||
|
contextRef.current.submit = (e) => {
|
||||||
|
if (disabled) {
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.stopPropagation();
|
||||||
|
setSubmitted(true);
|
||||||
|
|
||||||
|
const isValid = contextRef.current.validateForm();
|
||||||
|
|
||||||
|
// If not valid, prevent submission
|
||||||
|
if (!isValid) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
addStatus({
|
||||||
|
message: 'Please correct the fields below.',
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth',
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If submit handler comes through via props, run that
|
||||||
|
if (onSubmit) {
|
||||||
|
e.preventDefault();
|
||||||
|
return onSubmit(fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth',
|
||||||
|
});
|
||||||
|
|
||||||
|
const formData = contextRef.current.createFormData();
|
||||||
|
setProcessing(true);
|
||||||
|
|
||||||
|
// Make the API call from the action
|
||||||
|
return requests[method.toLowerCase()](action, {
|
||||||
|
body: formData,
|
||||||
|
}).then((res) => {
|
||||||
|
setModified(false);
|
||||||
|
if (typeof handleResponse === 'function') return handleResponse(res);
|
||||||
|
|
||||||
|
return res.json().then((json) => {
|
||||||
|
setProcessing(false);
|
||||||
|
clearStatus();
|
||||||
|
|
||||||
|
if (res.status < 400) {
|
||||||
|
if (typeof onSuccess === 'function') onSuccess(json);
|
||||||
|
|
||||||
|
if (redirect) {
|
||||||
|
const destination = {
|
||||||
|
pathname: redirect,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (json.message && !disableSuccessStatus) {
|
||||||
|
destination.state = {
|
||||||
|
status: [
|
||||||
|
{
|
||||||
|
message: json.message,
|
||||||
|
type: 'success',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
history.push(destination);
|
||||||
|
} else if (!disableSuccessStatus) {
|
||||||
|
replaceStatus([{
|
||||||
|
message: json.message,
|
||||||
|
type: 'success',
|
||||||
|
disappear: 3000,
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (json.message) {
|
||||||
|
addStatus({
|
||||||
|
message: json.message,
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(json.errors)) {
|
||||||
|
const [fieldErrors, nonFieldErrors] = json.errors.reduce(([fieldErrs, nonFieldErrs], err) => (err.field && err.message ? [[...fieldErrs, err], nonFieldErrs] : [fieldErrs, [...nonFieldErrs, err]]), [[], []]);
|
||||||
|
|
||||||
|
fieldErrors.forEach((err) => {
|
||||||
|
dispatchFields({
|
||||||
|
valid: false,
|
||||||
|
errorMessage: err.message,
|
||||||
|
path: err.field,
|
||||||
|
value: contextRef.current.fields?.[err.field]?.value,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
nonFieldErrors.forEach((err) => {
|
||||||
|
addStatus({
|
||||||
|
message: err.message || 'An unknown error occurred.',
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fieldErrors.length > 0 && nonFieldErrors.length === 0) {
|
||||||
|
addStatus({
|
||||||
|
message: 'Please correct the fields below.',
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
addStatus({
|
||||||
|
message: 'An unknown error occurred.',
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
}).catch((err) => {
|
||||||
|
addStatus({
|
||||||
|
message: err,
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
contextRef.current.getFields = () => contextRef.current.fields;
|
||||||
|
|
||||||
|
contextRef.current.getField = (path) => contextRef.current.fields[path];
|
||||||
|
|
||||||
|
contextRef.current.getData = () => reduceFieldsToValues(contextRef.current.fields, true);
|
||||||
|
|
||||||
|
contextRef.current.getSiblingData = (path) => {
|
||||||
|
let siblingFields = contextRef.current.fields;
|
||||||
|
|
||||||
|
// If this field is nested
|
||||||
|
// We can provide a list of sibling fields
|
||||||
|
if (path.indexOf('.') > 0) {
|
||||||
|
const parentFieldPath = path.substring(0, path.lastIndexOf('.') + 1);
|
||||||
|
siblingFields = Object.keys(contextRef.current.fields).reduce((siblings, fieldKey) => {
|
||||||
|
if (fieldKey.indexOf(parentFieldPath) === 0) {
|
||||||
|
return {
|
||||||
|
...siblings,
|
||||||
|
[fieldKey.replace(parentFieldPath, '')]: contextRef.current.fields[fieldKey],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return siblings;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
return reduceFieldsToValues(siblingFields, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
contextRef.current.getDataByPath = (path) => {
|
||||||
|
const pathPrefixToRemove = path.substring(0, path.lastIndexOf('.') + 1);
|
||||||
|
const name = path.split('.').pop();
|
||||||
|
|
||||||
|
const data = Object.keys(contextRef.current.fields).reduce((matchedData, key) => {
|
||||||
|
if (key.indexOf(`${path}.`) === 0) {
|
||||||
|
return {
|
||||||
|
...matchedData,
|
||||||
|
[key.replace(pathPrefixToRemove, '')]: contextRef.current.fields[key],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchedData;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const values = reduceFieldsToValues(data, true);
|
||||||
|
const unflattenedData = unflatten(values);
|
||||||
|
return unflattenedData?.[name];
|
||||||
|
};
|
||||||
|
|
||||||
|
contextRef.current.getUnflattenedValues = () => reduceFieldsToValues(contextRef.current.fields);
|
||||||
|
|
||||||
|
contextRef.current.validateForm = () => !Object.values(contextRef.current.fields).some((field) => field.valid === false);
|
||||||
|
|
||||||
|
contextRef.current.createFormData = () => {
|
||||||
|
const data = reduceFieldsToValues(contextRef.current.fields);
|
||||||
|
return objectToFormData(data, { indices: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
contextRef.current.setModified = setModified;
|
||||||
|
contextRef.current.setProcessing = setProcessing;
|
||||||
|
contextRef.current.setSubmitted = setSubmitted;
|
||||||
|
|
||||||
|
useThrottledEffect(() => {
|
||||||
|
refreshCookie();
|
||||||
|
}, 15000, [fields]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setModified(false);
|
||||||
|
}, [locale]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormContext.Provider value={contextRef.current}>
|
||||||
|
<FieldContext.Provider value={{
|
||||||
|
fields,
|
||||||
|
...contextRef.current,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SubmittedContext.Provider value={submitted}>
|
||||||
|
<ProcessingContext.Provider value={processing}>
|
||||||
|
<ModifiedContext.Provider value={modified}>
|
||||||
|
{children}
|
||||||
|
</ModifiedContext.Provider>
|
||||||
|
</ProcessingContext.Provider>
|
||||||
|
</SubmittedContext.Provider>
|
||||||
|
</FieldContext.Provider>
|
||||||
|
</FormContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
FormProvider.defaultProps = {
|
||||||
|
redirect: '',
|
||||||
|
onSubmit: null,
|
||||||
|
method: 'POST',
|
||||||
|
action: '',
|
||||||
|
handleResponse: null,
|
||||||
|
onSuccess: null,
|
||||||
|
className: '',
|
||||||
|
disableSuccessStatus: false,
|
||||||
|
disabled: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
FormProvider.propTypes = {
|
||||||
|
disableSuccessStatus: PropTypes.bool,
|
||||||
|
onSubmit: PropTypes.func,
|
||||||
|
method: PropTypes.oneOf(['post', 'POST', 'get', 'GET', 'put', 'PUT', 'delete', 'DELETE']),
|
||||||
|
action: PropTypes.string,
|
||||||
|
handleResponse: PropTypes.func,
|
||||||
|
onSuccess: PropTypes.func,
|
||||||
|
children: PropTypes.oneOfType([
|
||||||
|
PropTypes.arrayOf(PropTypes.node),
|
||||||
|
PropTypes.node,
|
||||||
|
]).isRequired,
|
||||||
|
className: PropTypes.string,
|
||||||
|
redirect: PropTypes.string,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormProvider;
|
||||||
49
src/client/components/forms/SubForm/index.js
Normal file
49
src/client/components/forms/SubForm/index.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import React, { useEffect, useCallback } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import FormProvider from '../FormProvider';
|
||||||
|
import { useFormFields } from '../FormProvider/context';
|
||||||
|
import useDebounce from '../../../hooks/useDebounce';
|
||||||
|
|
||||||
|
const SendValue = ({ sendValuesToParent }) => {
|
||||||
|
const { getFields } = useFormFields();
|
||||||
|
const fields = getFields();
|
||||||
|
|
||||||
|
const debouncedFields = useDebounce(fields, 500);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
sendValuesToParent(debouncedFields);
|
||||||
|
}, [sendValuesToParent, debouncedFields]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubForm = (props) => {
|
||||||
|
const { path: pathFromProps, name, children } = props;
|
||||||
|
const path = pathFromProps || name;
|
||||||
|
|
||||||
|
const { dispatchFields } = useFormFields();
|
||||||
|
|
||||||
|
const sendValuesToParent = useCallback((fields) => {
|
||||||
|
dispatchFields({ type: 'REPLACE_ALL_BY_PATH', value: fields, path });
|
||||||
|
}, [dispatchFields, path]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormProvider>
|
||||||
|
{children}
|
||||||
|
<SendValue sendValuesToParent={sendValuesToParent} />
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
SubForm.defaultProps = {
|
||||||
|
path: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
SubForm.propTypes = {
|
||||||
|
path: PropTypes.string,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SubForm;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useFormProcessing } from '../Form/context';
|
import { useFormProcessing } from '../FormProvider/context';
|
||||||
import Button from '../../elements/Button';
|
import Button from '../../elements/Button';
|
||||||
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ import Button from '../../../elements/Button';
|
|||||||
import DraggableSection from '../../DraggableSection';
|
import DraggableSection from '../../DraggableSection';
|
||||||
import reducer from '../rowReducer';
|
import reducer from '../rowReducer';
|
||||||
import { useRenderedFields } from '../../RenderFields';
|
import { useRenderedFields } from '../../RenderFields';
|
||||||
import { useForm } from '../../Form/context';
|
import { useForm } from '../../FormProvider/context';
|
||||||
import useFieldType from '../../useFieldType';
|
import useFieldType from '../../useFieldType';
|
||||||
import Error from '../../Error';
|
import Error from '../../Error';
|
||||||
import { array } from '../../../../../fields/validations';
|
import { array } from '../../../../../fields/validations';
|
||||||
|
import SubForm from '../../SubForm';
|
||||||
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
@@ -191,6 +192,7 @@ ArrayFieldType.defaultProps = {
|
|||||||
minRows: undefined,
|
minRows: undefined,
|
||||||
singularLabel: 'Row',
|
singularLabel: 'Row',
|
||||||
permissions: {},
|
permissions: {},
|
||||||
|
path: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
ArrayFieldType.propTypes = {
|
ArrayFieldType.propTypes = {
|
||||||
@@ -206,7 +208,7 @@ ArrayFieldType.propTypes = {
|
|||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
singularLabel: PropTypes.string,
|
singularLabel: PropTypes.string,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string,
|
||||||
fieldTypes: PropTypes.shape({}).isRequired,
|
fieldTypes: PropTypes.shape({}).isRequired,
|
||||||
validate: PropTypes.func,
|
validate: PropTypes.func,
|
||||||
required: PropTypes.bool,
|
required: PropTypes.bool,
|
||||||
@@ -217,4 +219,23 @@ ArrayFieldType.propTypes = {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withCondition(ArrayFieldType);
|
const ArrayForm = (props) => {
|
||||||
|
const { name, path } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SubForm {...{ name, path }}>
|
||||||
|
<ArrayFieldType {...props} />
|
||||||
|
</SubForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ArrayForm.defaultProps = {
|
||||||
|
path: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
ArrayForm.propTypes = {
|
||||||
|
path: PropTypes.string,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withCondition(ArrayForm);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import Label from '../../Label';
|
|||||||
import Button from '../../../elements/Button';
|
import Button from '../../../elements/Button';
|
||||||
import CopyToClipboard from '../../../elements/CopyToClipboard';
|
import CopyToClipboard from '../../../elements/CopyToClipboard';
|
||||||
import { text } from '../../../../../fields/validations';
|
import { text } from '../../../../../fields/validations';
|
||||||
import { useFormFields } from '../../Form/context';
|
import { useFormFields } from '../../FormProvider/context';
|
||||||
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Password from '../Password';
|
|||||||
import Checkbox from '../Checkbox';
|
import Checkbox from '../Checkbox';
|
||||||
import Button from '../../../elements/Button';
|
import Button from '../../../elements/Button';
|
||||||
import ConfirmPassword from '../ConfirmPassword';
|
import ConfirmPassword from '../ConfirmPassword';
|
||||||
import { useFormFields } from '../../Form/context';
|
import { useFormFields } from '../../FormProvider/context';
|
||||||
import APIKey from './APIKey';
|
import APIKey from './APIKey';
|
||||||
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|||||||
@@ -8,14 +8,15 @@ import { v4 as uuidv4 } from 'uuid';
|
|||||||
import withCondition from '../../withCondition';
|
import withCondition from '../../withCondition';
|
||||||
import Button from '../../../elements/Button';
|
import Button from '../../../elements/Button';
|
||||||
import reducer from '../rowReducer';
|
import reducer from '../rowReducer';
|
||||||
import { useForm } from '../../Form/context';
|
import { useForm } from '../../FormProvider/context';
|
||||||
import DraggableSection from '../../DraggableSection';
|
import DraggableSection from '../../DraggableSection';
|
||||||
import { useRenderedFields } from '../../RenderFields';
|
import { useRenderedFields } from '../../RenderFields';
|
||||||
import Error from '../../Error';
|
import Error from '../../Error';
|
||||||
import useFieldType from '../../useFieldType';
|
import useFieldType from '../../useFieldType';
|
||||||
import Popup from '../../../elements/Popup';
|
import Popup from '../../../elements/Popup';
|
||||||
import BlockSelector from './BlockSelector';
|
import BlockSelector from './BlockSelector';
|
||||||
import { blocks } from '../../../../../fields/validations';
|
import { blocks as blockValidator } from '../../../../../fields/validations';
|
||||||
|
import SubForm from '../../SubForm';
|
||||||
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
@@ -242,7 +243,7 @@ Blocks.defaultProps = {
|
|||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
initialData: [],
|
initialData: [],
|
||||||
singularLabel: 'Block',
|
singularLabel: 'Block',
|
||||||
validate: blocks,
|
validate: blockValidator,
|
||||||
required: false,
|
required: false,
|
||||||
maxRows: undefined,
|
maxRows: undefined,
|
||||||
minRows: undefined,
|
minRows: undefined,
|
||||||
@@ -276,4 +277,23 @@ Blocks.propTypes = {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withCondition(Blocks);
|
const BlocksForm = (props) => {
|
||||||
|
const { name, path } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SubForm {...{ name, path }}>
|
||||||
|
<Blocks {...props} />
|
||||||
|
</SubForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocksForm.defaultProps = {
|
||||||
|
path: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
BlocksForm.propTypes = {
|
||||||
|
path: PropTypes.string,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withCondition(BlocksForm);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useCallback } from 'react';
|
|||||||
import useFieldType from '../../useFieldType';
|
import useFieldType from '../../useFieldType';
|
||||||
import Label from '../../Label';
|
import Label from '../../Label';
|
||||||
import Error from '../../Error';
|
import Error from '../../Error';
|
||||||
import { useFormFields } from '../../Form/context';
|
import { useFormFields } from '../../FormProvider/context';
|
||||||
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import RenderFields, { useRenderedFields } from '../../RenderFields';
|
import RenderFields, { useRenderedFields } from '../../RenderFields';
|
||||||
import withCondition from '../../withCondition';
|
import withCondition from '../../withCondition';
|
||||||
|
import SubForm from '../../SubForm';
|
||||||
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
@@ -47,4 +48,23 @@ Group.propTypes = {
|
|||||||
fieldTypes: PropTypes.shape({}).isRequired,
|
fieldTypes: PropTypes.shape({}).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withCondition(Group);
|
const GroupForm = (props) => {
|
||||||
|
const { name, path } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SubForm {...{ name, path }}>
|
||||||
|
<Group {...props} />
|
||||||
|
</SubForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
GroupForm.defaultProps = {
|
||||||
|
path: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
GroupForm.propTypes = {
|
||||||
|
path: PropTypes.string,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withCondition(GroupForm);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Modal, useModal } from '@faceless-ui/modal';
|
|||||||
import config from '../../../../../config';
|
import config from '../../../../../config';
|
||||||
import MinimalTemplate from '../../../../templates/Minimal';
|
import MinimalTemplate from '../../../../templates/Minimal';
|
||||||
import Form from '../../../Form';
|
import Form from '../../../Form';
|
||||||
|
import FormProvider from '../../../FormProvider';
|
||||||
import Button from '../../../../elements/Button';
|
import Button from '../../../../elements/Button';
|
||||||
import formatFields from '../../../../views/collections/Edit/formatFields';
|
import formatFields from '../../../../views/collections/Edit/formatFields';
|
||||||
import RenderFields from '../../../RenderFields';
|
import RenderFields from '../../../RenderFields';
|
||||||
@@ -45,34 +46,36 @@ const AddUploadModal = (props) => {
|
|||||||
slug={slug}
|
slug={slug}
|
||||||
>
|
>
|
||||||
<MinimalTemplate width="wide">
|
<MinimalTemplate width="wide">
|
||||||
<Form
|
<FormProvider
|
||||||
method="post"
|
method="post"
|
||||||
action={`${serverURL}${api}/${collection.slug}`}
|
action={`${serverURL}${api}/${collection.slug}`}
|
||||||
onSuccess={onSuccess}
|
onSuccess={onSuccess}
|
||||||
disableSuccessStatus
|
disableSuccessStatus
|
||||||
>
|
>
|
||||||
<header className={`${baseClass}__header`}>
|
<Form>
|
||||||
<h1>
|
<header className={`${baseClass}__header`}>
|
||||||
New
|
<h1>
|
||||||
{' '}
|
New
|
||||||
{collection.labels.singular}
|
{' '}
|
||||||
</h1>
|
{collection.labels.singular}
|
||||||
<FormSubmit>Save</FormSubmit>
|
</h1>
|
||||||
<Button
|
<FormSubmit>Save</FormSubmit>
|
||||||
icon="x"
|
<Button
|
||||||
round
|
icon="x"
|
||||||
buttonStyle="icon-label"
|
round
|
||||||
iconStyle="with-border"
|
buttonStyle="icon-label"
|
||||||
onClick={closeAll}
|
iconStyle="with-border"
|
||||||
|
onClick={closeAll}
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
<RenderFields
|
||||||
|
filter={(field) => (!field.position || (field.position && field.position !== 'sidebar'))}
|
||||||
|
fieldTypes={fieldTypes}
|
||||||
|
fieldSchema={fields}
|
||||||
|
customComponentsPath={`${collection.slug}.fields.`}
|
||||||
/>
|
/>
|
||||||
</header>
|
</Form>
|
||||||
<RenderFields
|
</FormProvider>
|
||||||
filter={(field) => (!field.position || (field.position && field.position !== 'sidebar'))}
|
|
||||||
fieldTypes={fieldTypes}
|
|
||||||
fieldSchema={fields}
|
|
||||||
customComponentsPath={`${collection.slug}.fields.`}
|
|
||||||
/>
|
|
||||||
</Form>
|
|
||||||
</MinimalTemplate>
|
</MinimalTemplate>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
useCallback, useEffect, useState,
|
useCallback, useEffect, useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { useFormProcessing, useFormSubmitted, useFormModified, useForm } from '../Form/context';
|
import { useFormProcessing, useFormSubmitted, useFormModified, useForm } from '../FormProvider/context';
|
||||||
import useDebounce from '../../../hooks/useDebounce';
|
import useDebounce from '../../../hooks/useDebounce';
|
||||||
import useUnmountEffect from '../../../hooks/useUnmountEffect';
|
import useUnmountEffect from '../../../hooks/useUnmountEffect';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useFormFields } from '../Form/context';
|
import { useFormFields } from '../FormProvider/context';
|
||||||
|
|
||||||
const withCondition = (Field) => {
|
const withCondition = (Field) => {
|
||||||
const CheckForCondition = (props) => {
|
const CheckForCondition = (props) => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import NavigationPrompt from 'react-router-navigation-prompt';
|
import NavigationPrompt from 'react-router-navigation-prompt';
|
||||||
import { useForm } from '../../forms/Form/context';
|
import { useForm } from '../../forms/FormProvider/context';
|
||||||
import MinimalTemplate from '../../templates/Minimal';
|
import MinimalTemplate from '../../templates/Minimal';
|
||||||
import Button from '../../elements/Button';
|
import Button from '../../elements/Button';
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import format from 'date-fns/format';
|
|||||||
import config from 'payload/config';
|
import config from 'payload/config';
|
||||||
import Eyebrow from '../../elements/Eyebrow';
|
import Eyebrow from '../../elements/Eyebrow';
|
||||||
import Form from '../../forms/Form';
|
import Form from '../../forms/Form';
|
||||||
|
import FormProvider from '../../forms/FormProvider';
|
||||||
import PreviewButton from '../../elements/PreviewButton';
|
import PreviewButton from '../../elements/PreviewButton';
|
||||||
import FormSubmit from '../../forms/Submit';
|
import FormSubmit from '../../forms/Submit';
|
||||||
import RenderFields from '../../forms/RenderFields';
|
import RenderFields from '../../forms/RenderFields';
|
||||||
@@ -48,93 +49,95 @@ const DefaultAccount = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes}>
|
<div className={classes}>
|
||||||
<Form
|
<FormProvider
|
||||||
className={`${baseClass}__form`}
|
className={`${baseClass}__form`}
|
||||||
method="put"
|
method="put"
|
||||||
action={`${serverURL}${api}/${slug}/${data?.id}`}
|
action={`${serverURL}${api}/${slug}/${data?.id}`}
|
||||||
>
|
>
|
||||||
<div className={`${baseClass}__main`}>
|
<Form>
|
||||||
<Eyebrow />
|
<div className={`${baseClass}__main`}>
|
||||||
<LeaveWithoutSaving />
|
<Eyebrow />
|
||||||
<div className={`${baseClass}__edit`}>
|
<LeaveWithoutSaving />
|
||||||
<header className={`${baseClass}__header`}>
|
<div className={`${baseClass}__edit`}>
|
||||||
<h1>
|
<header className={`${baseClass}__header`}>
|
||||||
<RenderTitle {...{ data, useAsTitle, fallback: '[Untitled]' }} />
|
<h1>
|
||||||
</h1>
|
<RenderTitle {...{ data, useAsTitle, fallback: '[Untitled]' }} />
|
||||||
</header>
|
</h1>
|
||||||
<RenderFields
|
</header>
|
||||||
filter={(field) => (!field.position || (field.position && field.position !== 'sidebar'))}
|
<RenderFields
|
||||||
fieldTypes={fieldTypes}
|
filter={(field) => (!field.position || (field.position && field.position !== 'sidebar'))}
|
||||||
fieldSchema={fields}
|
fieldTypes={fieldTypes}
|
||||||
initialData={dataToRender}
|
fieldSchema={fields}
|
||||||
customComponentsPath={`${slug}.fields.`}
|
initialData={dataToRender}
|
||||||
/>
|
customComponentsPath={`${slug}.fields.`}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={`${baseClass}__sidebar`}>
|
|
||||||
<ul className={`${baseClass}__collection-actions`}>
|
|
||||||
<li><Link to={`${admin}/collections/${slug}/create`}>Create New</Link></li>
|
|
||||||
<li><DuplicateDocument slug={slug} /></li>
|
|
||||||
<li>
|
|
||||||
<DeleteDocument
|
|
||||||
collection={collection}
|
|
||||||
id={data?.id}
|
|
||||||
/>
|
/>
|
||||||
</li>
|
</div>
|
||||||
</ul>
|
|
||||||
<div className={`${baseClass}__document-actions${preview ? ` ${baseClass}__document-actions--with-preview` : ''}`}>
|
|
||||||
<PreviewButton generatePreviewURL={preview} />
|
|
||||||
<FormSubmit>Save</FormSubmit>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={`${baseClass}__api-url`}>
|
<div className={`${baseClass}__sidebar`}>
|
||||||
<span className={`${baseClass}__label`}>
|
<ul className={`${baseClass}__collection-actions`}>
|
||||||
API URL
|
<li><Link to={`${admin}/collections/${slug}/create`}>Create New</Link></li>
|
||||||
{' '}
|
<li><DuplicateDocument slug={slug} /></li>
|
||||||
<CopyToClipboard value={apiURL} />
|
<li>
|
||||||
</span>
|
<DeleteDocument
|
||||||
<a
|
collection={collection}
|
||||||
href={apiURL}
|
id={data?.id}
|
||||||
target="_blank"
|
/>
|
||||||
rel="noopener noreferrer"
|
</li>
|
||||||
>
|
</ul>
|
||||||
{apiURL}
|
<div className={`${baseClass}__document-actions${preview ? ` ${baseClass}__document-actions--with-preview` : ''}`}>
|
||||||
</a>
|
<PreviewButton generatePreviewURL={preview} />
|
||||||
</div>
|
<FormSubmit>Save</FormSubmit>
|
||||||
<div className={`${baseClass}__sidebar-fields`}>
|
</div>
|
||||||
<RenderFields
|
<div className={`${baseClass}__api-url`}>
|
||||||
filter={(field) => field.position === 'sidebar'}
|
<span className={`${baseClass}__label`}>
|
||||||
position="sidebar"
|
API URL
|
||||||
fieldTypes={fieldTypes}
|
{' '}
|
||||||
fieldSchema={fields}
|
<CopyToClipboard value={apiURL} />
|
||||||
initialData={dataToRender}
|
</span>
|
||||||
customComponentsPath={`${slug}.fields.`}
|
<a
|
||||||
/>
|
href={apiURL}
|
||||||
</div>
|
target="_blank"
|
||||||
<ul className={`${baseClass}__meta`}>
|
rel="noopener noreferrer"
|
||||||
<li>
|
>
|
||||||
<div className={`${baseClass}__label`}>ID</div>
|
{apiURL}
|
||||||
<div>{data?.id}</div>
|
</a>
|
||||||
</li>
|
</div>
|
||||||
{timestamps && (
|
<div className={`${baseClass}__sidebar-fields`}>
|
||||||
<React.Fragment>
|
<RenderFields
|
||||||
{data.updatedAt && (
|
filter={(field) => field.position === 'sidebar'}
|
||||||
<li>
|
position="sidebar"
|
||||||
<div className={`${baseClass}__label`}>Last Modified</div>
|
fieldTypes={fieldTypes}
|
||||||
<div>{format(new Date(data.updatedAt), 'MMMM do yyyy, h:mma')}</div>
|
fieldSchema={fields}
|
||||||
</li>
|
initialData={dataToRender}
|
||||||
)}
|
customComponentsPath={`${slug}.fields.`}
|
||||||
{data.createdAt && (
|
/>
|
||||||
<li>
|
</div>
|
||||||
<div className={`${baseClass}__label`}>Created</div>
|
<ul className={`${baseClass}__meta`}>
|
||||||
<div>{format(new Date(data.createdAt), 'MMMM do yyyy, h:mma')}</div>
|
<li>
|
||||||
</li>
|
<div className={`${baseClass}__label`}>ID</div>
|
||||||
)}
|
<div>{data?.id}</div>
|
||||||
</React.Fragment>
|
</li>
|
||||||
)}
|
{timestamps && (
|
||||||
|
<React.Fragment>
|
||||||
|
{data.updatedAt && (
|
||||||
|
<li>
|
||||||
|
<div className={`${baseClass}__label`}>Last Modified</div>
|
||||||
|
<div>{format(new Date(data.updatedAt), 'MMMM do yyyy, h:mma')}</div>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
{data.createdAt && (
|
||||||
|
<li>
|
||||||
|
<div className={`${baseClass}__label`}>Created</div>
|
||||||
|
<div>{format(new Date(data.createdAt), 'MMMM do yyyy, h:mma')}</div>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
|
</FormProvider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -163,6 +166,7 @@ DefaultAccount.propTypes = {
|
|||||||
data: PropTypes.shape({
|
data: PropTypes.shape({
|
||||||
updatedAt: PropTypes.string,
|
updatedAt: PropTypes.string,
|
||||||
createdAt: PropTypes.string,
|
createdAt: PropTypes.string,
|
||||||
|
id: PropTypes.string,
|
||||||
}),
|
}),
|
||||||
onSave: PropTypes.func,
|
onSave: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import config from 'payload/config';
|
import config from 'payload/config';
|
||||||
import MinimalTemplate from '../../templates/Minimal';
|
import MinimalTemplate from '../../templates/Minimal';
|
||||||
|
import FormProvider from '../../forms/FormProvider';
|
||||||
import Form from '../../forms/Form';
|
import Form from '../../forms/Form';
|
||||||
import RenderFields from '../../forms/RenderFields';
|
import RenderFields from '../../forms/RenderFields';
|
||||||
import * as fieldTypes from '../../forms/field-types';
|
import * as fieldTypes from '../../forms/field-types';
|
||||||
@@ -48,21 +49,23 @@ const CreateFirstUser = (props) => {
|
|||||||
<MinimalTemplate className={baseClass}>
|
<MinimalTemplate className={baseClass}>
|
||||||
<h1>Welcome to Payload</h1>
|
<h1>Welcome to Payload</h1>
|
||||||
<p>To begin, create your first user.</p>
|
<p>To begin, create your first user.</p>
|
||||||
<Form
|
<FormProvider
|
||||||
onSuccess={onSuccess}
|
onSuccess={onSuccess}
|
||||||
method="POST"
|
method="POST"
|
||||||
redirect={admin}
|
redirect={admin}
|
||||||
action={`${serverURL}${api}/${userSlug}/first-register`}
|
action={`${serverURL}${api}/${userSlug}/first-register`}
|
||||||
>
|
>
|
||||||
<RenderFields
|
<Form>
|
||||||
fieldSchema={[
|
<RenderFields
|
||||||
...fields,
|
fieldSchema={[
|
||||||
...userConfig.fields,
|
...fields,
|
||||||
]}
|
...userConfig.fields,
|
||||||
fieldTypes={fieldTypes}
|
]}
|
||||||
/>
|
fieldTypes={fieldTypes}
|
||||||
<FormSubmit>Create</FormSubmit>
|
/>
|
||||||
</Form>
|
<FormSubmit>Create</FormSubmit>
|
||||||
|
</Form>
|
||||||
|
</FormProvider>
|
||||||
</MinimalTemplate>
|
</MinimalTemplate>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Link } from 'react-router-dom';
|
|||||||
import config from 'payload/config';
|
import config from 'payload/config';
|
||||||
import MinimalTemplate from '../../templates/Minimal';
|
import MinimalTemplate from '../../templates/Minimal';
|
||||||
import StatusList, { useStatusList } from '../../elements/Status';
|
import StatusList, { useStatusList } from '../../elements/Status';
|
||||||
|
import FormProvider from '../../forms/FormProvider';
|
||||||
import Form from '../../forms/Form';
|
import Form from '../../forms/Form';
|
||||||
import Email from '../../forms/field-types/Email';
|
import Email from '../../forms/field-types/Email';
|
||||||
import FormSubmit from '../../forms/Submit';
|
import FormSubmit from '../../forms/Submit';
|
||||||
@@ -84,21 +85,22 @@ const ForgotPassword = () => {
|
|||||||
return (
|
return (
|
||||||
<MinimalTemplate className={baseClass}>
|
<MinimalTemplate className={baseClass}>
|
||||||
<StatusList />
|
<StatusList />
|
||||||
<Form
|
<FormProvider
|
||||||
novalidate
|
|
||||||
handleResponse={handleResponse}
|
handleResponse={handleResponse}
|
||||||
method="POST"
|
method="POST"
|
||||||
action={`${serverURL}${api}/${userSlug}/forgot-password`}
|
action={`${serverURL}${api}/${userSlug}/forgot-password`}
|
||||||
>
|
>
|
||||||
<h1>Forgot Password</h1>
|
<Form>
|
||||||
<p>Please enter your email below. You will receive an email message with instructions on how to reset your password.</p>
|
<h1>Forgot Password</h1>
|
||||||
<Email
|
<p>Please enter your email below. You will receive an email message with instructions on how to reset your password.</p>
|
||||||
label="Email Address"
|
<Email
|
||||||
name="email"
|
label="Email Address"
|
||||||
required
|
name="email"
|
||||||
/>
|
required
|
||||||
<FormSubmit>Submit</FormSubmit>
|
/>
|
||||||
</Form>
|
<FormSubmit>Submit</FormSubmit>
|
||||||
|
</Form>
|
||||||
|
</FormProvider>
|
||||||
<Link to={`${admin}/login`}>Back to login</Link>
|
<Link to={`${admin}/login`}>Back to login</Link>
|
||||||
</MinimalTemplate>
|
</MinimalTemplate>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import format from 'date-fns/format';
|
import format from 'date-fns/format';
|
||||||
import config from 'payload/config';
|
import config from 'payload/config';
|
||||||
import Eyebrow from '../../elements/Eyebrow';
|
import Eyebrow from '../../elements/Eyebrow';
|
||||||
|
import FormProvider from '../../forms/FormProvider';
|
||||||
import Form from '../../forms/Form';
|
import Form from '../../forms/Form';
|
||||||
import PreviewButton from '../../elements/PreviewButton';
|
import PreviewButton from '../../elements/PreviewButton';
|
||||||
import FormSubmit from '../../forms/Submit';
|
import FormSubmit from '../../forms/Submit';
|
||||||
@@ -35,84 +36,85 @@ const DefaultGlobalView = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={baseClass}>
|
<div className={baseClass}>
|
||||||
<Form
|
<FormProvider
|
||||||
className={`${baseClass}__form`}
|
|
||||||
method="post"
|
method="post"
|
||||||
action={action}
|
action={action}
|
||||||
onSuccess={onSave}
|
onSuccess={onSave}
|
||||||
disabled={!hasSavePermission}
|
disabled={!hasSavePermission}
|
||||||
>
|
>
|
||||||
<div className={`${baseClass}__main`}>
|
<Form className={`${baseClass}__form`}>
|
||||||
<Eyebrow />
|
<div className={`${baseClass}__main`}>
|
||||||
<LeaveWithoutSaving />
|
<Eyebrow />
|
||||||
<div className={`${baseClass}__edit`}>
|
<LeaveWithoutSaving />
|
||||||
<header className={`${baseClass}__header`}>
|
<div className={`${baseClass}__edit`}>
|
||||||
<h1>
|
<header className={`${baseClass}__header`}>
|
||||||
Edit
|
<h1>
|
||||||
{' '}
|
Edit
|
||||||
{label}
|
{' '}
|
||||||
</h1>
|
{label}
|
||||||
</header>
|
</h1>
|
||||||
<RenderFields
|
</header>
|
||||||
operation="update"
|
<RenderFields
|
||||||
readOnly={!hasSavePermission}
|
operation="update"
|
||||||
permissions={permissions.fields}
|
readOnly={!hasSavePermission}
|
||||||
filter={field => (!field.position || (field.position && field.position !== 'sidebar'))}
|
permissions={permissions.fields}
|
||||||
fieldTypes={fieldTypes}
|
filter={(field) => (!field.position || (field.position && field.position !== 'sidebar'))}
|
||||||
fieldSchema={fields}
|
fieldTypes={fieldTypes}
|
||||||
initialData={data}
|
fieldSchema={fields}
|
||||||
customComponentsPath={`${slug}.fields.`}
|
initialData={data}
|
||||||
/>
|
customComponentsPath={`${slug}.fields.`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className={`${baseClass}__sidebar`}>
|
||||||
<div className={`${baseClass}__sidebar`}>
|
<div className={`${baseClass}__document-actions${preview ? ` ${baseClass}__document-actions--with-preview` : ''}`}>
|
||||||
<div className={`${baseClass}__document-actions${preview ? ` ${baseClass}__document-actions--with-preview` : ''}`}>
|
<PreviewButton generatePreviewURL={preview} />
|
||||||
<PreviewButton generatePreviewURL={preview} />
|
{hasSavePermission && (
|
||||||
{hasSavePermission && (
|
<FormSubmit>Save</FormSubmit>
|
||||||
<FormSubmit>Save</FormSubmit>
|
)}
|
||||||
|
</div>
|
||||||
|
{data && (
|
||||||
|
<div className={`${baseClass}__api-url`}>
|
||||||
|
<span className={`${baseClass}__label`}>
|
||||||
|
API URL
|
||||||
|
{' '}
|
||||||
|
<CopyToClipboard value={apiURL} />
|
||||||
|
</span>
|
||||||
|
<a
|
||||||
|
href={apiURL}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{apiURL}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={`${baseClass}__sidebar-fields`}>
|
||||||
|
<RenderFields
|
||||||
|
operation="update"
|
||||||
|
readOnly={!hasSavePermission}
|
||||||
|
permissions={permissions.fields}
|
||||||
|
filter={(field) => field.position === 'sidebar'}
|
||||||
|
position="sidebar"
|
||||||
|
fieldTypes={fieldTypes}
|
||||||
|
fieldSchema={fields}
|
||||||
|
initialData={data}
|
||||||
|
customComponentsPath={`${slug}.fields.`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{data && (
|
||||||
|
<ul className={`${baseClass}__meta`}>
|
||||||
|
{data.updatedAt && (
|
||||||
|
<li>
|
||||||
|
<div className={`${baseClass}__label`}>Last Modified</div>
|
||||||
|
<div>{format(new Date(data.updatedAt), 'MMMM do yyyy, h:mma')}</div>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{data && (
|
</Form>
|
||||||
<div className={`${baseClass}__api-url`}>
|
</FormProvider>
|
||||||
<span className={`${baseClass}__label`}>
|
|
||||||
API URL
|
|
||||||
{' '}
|
|
||||||
<CopyToClipboard value={apiURL} />
|
|
||||||
</span>
|
|
||||||
<a
|
|
||||||
href={apiURL}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{apiURL}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className={`${baseClass}__sidebar-fields`}>
|
|
||||||
<RenderFields
|
|
||||||
operation="update"
|
|
||||||
readOnly={!hasSavePermission}
|
|
||||||
permissions={permissions.fields}
|
|
||||||
filter={field => field.position === 'sidebar'}
|
|
||||||
position="sidebar"
|
|
||||||
fieldTypes={fieldTypes}
|
|
||||||
fieldSchema={fields}
|
|
||||||
initialData={data}
|
|
||||||
customComponentsPath={`${slug}.fields.`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{data && (
|
|
||||||
<ul className={`${baseClass}__meta`}>
|
|
||||||
{data.updatedAt && (
|
|
||||||
<li>
|
|
||||||
<div className={`${baseClass}__label`}>Last Modified</div>
|
|
||||||
<div>{format(new Date(data.updatedAt), 'MMMM do yyyy, h:mma')}</div>
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Link, useHistory } from 'react-router-dom';
|
|||||||
import config from 'payload/config';
|
import config from 'payload/config';
|
||||||
import Logo from '../../graphics/Logo';
|
import Logo from '../../graphics/Logo';
|
||||||
import MinimalTemplate from '../../templates/Minimal';
|
import MinimalTemplate from '../../templates/Minimal';
|
||||||
|
import FormProvider from '../../forms/FormProvider';
|
||||||
import Form from '../../forms/Form';
|
import Form from '../../forms/Form';
|
||||||
import Email from '../../forms/field-types/Email';
|
import Email from '../../forms/field-types/Email';
|
||||||
import Password from '../../forms/field-types/Password';
|
import Password from '../../forms/field-types/Password';
|
||||||
@@ -57,29 +58,31 @@ const Login = () => {
|
|||||||
<div className={`${baseClass}__brand`}>
|
<div className={`${baseClass}__brand`}>
|
||||||
<Logo />
|
<Logo />
|
||||||
</div>
|
</div>
|
||||||
<Form
|
<FormProvider
|
||||||
disableSuccessStatus
|
disableSuccessStatus
|
||||||
onSuccess={onSuccess}
|
onSuccess={onSuccess}
|
||||||
method="POST"
|
method="POST"
|
||||||
action={`${serverURL}${api}/${userSlug}/login`}
|
action={`${serverURL}${api}/${userSlug}/login`}
|
||||||
>
|
>
|
||||||
<Email
|
<Form>
|
||||||
label="Email Address"
|
<Email
|
||||||
name="email"
|
label="Email Address"
|
||||||
autoComplete="email"
|
name="email"
|
||||||
required
|
autoComplete="email"
|
||||||
/>
|
required
|
||||||
<Password
|
/>
|
||||||
error="password"
|
<Password
|
||||||
label="Password"
|
error="password"
|
||||||
name="password"
|
label="Password"
|
||||||
required
|
name="password"
|
||||||
/>
|
required
|
||||||
<Link to={`${admin}/forgot`}>
|
/>
|
||||||
Forgot password?
|
<Link to={`${admin}/forgot`}>
|
||||||
</Link>
|
Forgot password?
|
||||||
<FormSubmit>Login</FormSubmit>
|
</Link>
|
||||||
</Form>
|
<FormSubmit>Login</FormSubmit>
|
||||||
|
</Form>
|
||||||
|
</FormProvider>
|
||||||
</MinimalTemplate>
|
</MinimalTemplate>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import { Link, useHistory, useParams } from 'react-router-dom';
|
import { Link, useHistory, useParams } from 'react-router-dom';
|
||||||
import config from 'payload/config';
|
import config from 'payload/config';
|
||||||
import StatusList from '../../elements/Status';
|
import StatusList from '../../elements/Status';
|
||||||
|
import FormProvider from '../../forms/FormProvider';
|
||||||
import Form from '../../forms/Form';
|
import Form from '../../forms/Form';
|
||||||
import Password from '../../forms/field-types/Password';
|
import Password from '../../forms/field-types/Password';
|
||||||
import FormSubmit from '../../forms/Submit';
|
import FormSubmit from '../../forms/Submit';
|
||||||
@@ -59,24 +60,26 @@ const ResetPassword = () => {
|
|||||||
<div className={baseClass}>
|
<div className={baseClass}>
|
||||||
<div className={`${baseClass}__wrap`}>
|
<div className={`${baseClass}__wrap`}>
|
||||||
<StatusList />
|
<StatusList />
|
||||||
<Form
|
<FormProvider
|
||||||
handleResponse={handleResponse}
|
handleResponse={handleResponse}
|
||||||
method="POST"
|
method="POST"
|
||||||
action={`${serverURL}${api}/${userSlug}/reset-password`}
|
action={`${serverURL}${api}/${userSlug}/reset-password`}
|
||||||
redirect={admin}
|
redirect={admin}
|
||||||
>
|
>
|
||||||
<Password
|
<Form>
|
||||||
error="password"
|
<Password
|
||||||
label="Password"
|
error="password"
|
||||||
name="password"
|
label="Password"
|
||||||
required
|
name="password"
|
||||||
/>
|
required
|
||||||
<HiddenInput
|
/>
|
||||||
name="token"
|
<HiddenInput
|
||||||
defaultValue={token}
|
name="token"
|
||||||
/>
|
defaultValue={token}
|
||||||
<FormSubmit>Reset Password</FormSubmit>
|
/>
|
||||||
</Form>
|
<FormSubmit>Reset Password</FormSubmit>
|
||||||
|
</Form>
|
||||||
|
</FormProvider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Link, useRouteMatch } from 'react-router-dom';
|
|||||||
import format from 'date-fns/format';
|
import format from 'date-fns/format';
|
||||||
import config from 'payload/config';
|
import config from 'payload/config';
|
||||||
import Eyebrow from '../../../elements/Eyebrow';
|
import Eyebrow from '../../../elements/Eyebrow';
|
||||||
|
import FormProvider from '../../../forms/FormProvider';
|
||||||
import Form from '../../../forms/Form';
|
import Form from '../../../forms/Form';
|
||||||
import Loading from '../../../elements/Loading';
|
import Loading from '../../../elements/Loading';
|
||||||
import PreviewButton from '../../../elements/PreviewButton';
|
import PreviewButton from '../../../elements/PreviewButton';
|
||||||
@@ -55,130 +56,131 @@ const DefaultEditView = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes}>
|
<div className={classes}>
|
||||||
<Form
|
<FormProvider
|
||||||
className={`${baseClass}__form`}
|
|
||||||
method={id ? 'put' : 'post'}
|
method={id ? 'put' : 'post'}
|
||||||
action={action}
|
action={action}
|
||||||
onSuccess={onSave}
|
onSuccess={onSave}
|
||||||
disabled={!hasSavePermission}
|
disabled={!hasSavePermission}
|
||||||
>
|
>
|
||||||
<div className={`${baseClass}__main`}>
|
<Form className={`${baseClass}__form`}>
|
||||||
<Eyebrow />
|
<div className={`${baseClass}__main`}>
|
||||||
<LeaveWithoutSaving />
|
<Eyebrow />
|
||||||
<div className={`${baseClass}__edit`}>
|
<LeaveWithoutSaving />
|
||||||
{isLoading && (
|
<div className={`${baseClass}__edit`}>
|
||||||
<Loading />
|
{isLoading && (
|
||||||
)}
|
<Loading />
|
||||||
{!isLoading && (
|
)}
|
||||||
<React.Fragment>
|
{!isLoading && (
|
||||||
<header className={`${baseClass}__header`}>
|
|
||||||
<h1>
|
|
||||||
<RenderTitle {...{ data, useAsTitle, fallback: '[Untitled]' }} />
|
|
||||||
</h1>
|
|
||||||
</header>
|
|
||||||
<RenderFields
|
|
||||||
operation={isEditing ? 'update' : 'create'}
|
|
||||||
readOnly={!hasSavePermission}
|
|
||||||
permissions={permissions.fields}
|
|
||||||
filter={(field) => (!field?.admin?.position || (field?.admin?.position !== 'sidebar'))}
|
|
||||||
fieldTypes={fieldTypes}
|
|
||||||
fieldSchema={fields}
|
|
||||||
initialData={data}
|
|
||||||
customComponentsPath={`${slug}.fields.`}
|
|
||||||
/>
|
|
||||||
</React.Fragment>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={`${baseClass}__sidebar`}>
|
|
||||||
{isEditing ? (
|
|
||||||
<ul className={`${baseClass}__collection-actions`}>
|
|
||||||
{permissions?.create?.permission && (
|
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<li><Link to={`${admin}/collections/${slug}/create`}>Create New</Link></li>
|
<header className={`${baseClass}__header`}>
|
||||||
<li><DuplicateDocument slug={slug} /></li>
|
<h1>
|
||||||
</React.Fragment>
|
<RenderTitle {...{ data, useAsTitle, fallback: '[Untitled]' }} />
|
||||||
)}
|
</h1>
|
||||||
{permissions?.delete?.permission && (
|
</header>
|
||||||
<li>
|
|
||||||
<DeleteDocument
|
|
||||||
collection={collection}
|
|
||||||
id={id}
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
) : undefined}
|
|
||||||
<div className={`${baseClass}__sidebar-sticky`}>
|
|
||||||
<div className={`${baseClass}__document-actions${(preview && isEditing) ? ` ${baseClass}__document-actions--with-preview` : ''}`}>
|
|
||||||
{isEditing && (
|
|
||||||
<PreviewButton generatePreviewURL={preview} />
|
|
||||||
)}
|
|
||||||
{hasSavePermission && (
|
|
||||||
<FormSubmit>Save</FormSubmit>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{isEditing && (
|
|
||||||
<div className={`${baseClass}__api-url`}>
|
|
||||||
<span className={`${baseClass}__label`}>
|
|
||||||
API URL
|
|
||||||
{' '}
|
|
||||||
<CopyToClipboard value={apiURL} />
|
|
||||||
</span>
|
|
||||||
<a
|
|
||||||
href={apiURL}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{apiURL}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!isLoading && (
|
|
||||||
<React.Fragment>
|
|
||||||
<div className={`${baseClass}__sidebar-fields`}>
|
|
||||||
<RenderFields
|
<RenderFields
|
||||||
operation={isEditing ? 'update' : 'create'}
|
operation={isEditing ? 'update' : 'create'}
|
||||||
readOnly={!hasSavePermission}
|
readOnly={!hasSavePermission}
|
||||||
permissions={permissions.fields}
|
permissions={permissions.fields}
|
||||||
filter={(field) => field?.admin?.position === 'sidebar'}
|
filter={(field) => (!field?.admin?.position || (field?.admin?.position !== 'sidebar'))}
|
||||||
position="sidebar"
|
|
||||||
fieldTypes={fieldTypes}
|
fieldTypes={fieldTypes}
|
||||||
fieldSchema={fields}
|
fieldSchema={fields}
|
||||||
initialData={data}
|
initialData={data}
|
||||||
customComponentsPath={`${slug}.fields.`}
|
customComponentsPath={`${slug}.fields.`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</React.Fragment>
|
||||||
{isEditing && (
|
)}
|
||||||
<ul className={`${baseClass}__meta`}>
|
</div>
|
||||||
<li>
|
|
||||||
<div className={`${baseClass}__label`}>ID</div>
|
|
||||||
<div>{id}</div>
|
|
||||||
</li>
|
|
||||||
{timestamps && (
|
|
||||||
<React.Fragment>
|
|
||||||
{data.updatedAt && (
|
|
||||||
<li>
|
|
||||||
<div className={`${baseClass}__label`}>Last Modified</div>
|
|
||||||
<div>{format(new Date(data.updatedAt), 'MMMM do yyyy, h:mma')}</div>
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
{data.createdAt && (
|
|
||||||
<li>
|
|
||||||
<div className={`${baseClass}__label`}>Created</div>
|
|
||||||
<div>{format(new Date(data.createdAt), 'MMMM do yyyy, h:mma')}</div>
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className={`${baseClass}__sidebar`}>
|
||||||
</Form>
|
{isEditing ? (
|
||||||
|
<ul className={`${baseClass}__collection-actions`}>
|
||||||
|
{permissions?.create?.permission && (
|
||||||
|
<React.Fragment>
|
||||||
|
<li><Link to={`${admin}/collections/${slug}/create`}>Create New</Link></li>
|
||||||
|
<li><DuplicateDocument slug={slug} /></li>
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
|
{permissions?.delete?.permission && (
|
||||||
|
<li>
|
||||||
|
<DeleteDocument
|
||||||
|
collection={collection}
|
||||||
|
id={id}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
) : undefined}
|
||||||
|
<div className={`${baseClass}__sidebar-sticky`}>
|
||||||
|
<div className={`${baseClass}__document-actions${(preview && isEditing) ? ` ${baseClass}__document-actions--with-preview` : ''}`}>
|
||||||
|
{isEditing && (
|
||||||
|
<PreviewButton generatePreviewURL={preview} />
|
||||||
|
)}
|
||||||
|
{hasSavePermission && (
|
||||||
|
<FormSubmit>Save</FormSubmit>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{isEditing && (
|
||||||
|
<div className={`${baseClass}__api-url`}>
|
||||||
|
<span className={`${baseClass}__label`}>
|
||||||
|
API URL
|
||||||
|
{' '}
|
||||||
|
<CopyToClipboard value={apiURL} />
|
||||||
|
</span>
|
||||||
|
<a
|
||||||
|
href={apiURL}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{apiURL}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!isLoading && (
|
||||||
|
<React.Fragment>
|
||||||
|
<div className={`${baseClass}__sidebar-fields`}>
|
||||||
|
<RenderFields
|
||||||
|
operation={isEditing ? 'update' : 'create'}
|
||||||
|
readOnly={!hasSavePermission}
|
||||||
|
permissions={permissions.fields}
|
||||||
|
filter={(field) => field?.admin?.position === 'sidebar'}
|
||||||
|
position="sidebar"
|
||||||
|
fieldTypes={fieldTypes}
|
||||||
|
fieldSchema={fields}
|
||||||
|
initialData={data}
|
||||||
|
customComponentsPath={`${slug}.fields.`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{isEditing && (
|
||||||
|
<ul className={`${baseClass}__meta`}>
|
||||||
|
<li>
|
||||||
|
<div className={`${baseClass}__label`}>ID</div>
|
||||||
|
<div>{id}</div>
|
||||||
|
</li>
|
||||||
|
{timestamps && (
|
||||||
|
<React.Fragment>
|
||||||
|
{data.updatedAt && (
|
||||||
|
<li>
|
||||||
|
<div className={`${baseClass}__label`}>Last Modified</div>
|
||||||
|
<div>{format(new Date(data.updatedAt), 'MMMM do yyyy, h:mma')}</div>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
{data.createdAt && (
|
||||||
|
<li>
|
||||||
|
<div className={`${baseClass}__label`}>Created</div>
|
||||||
|
<div>{format(new Date(data.createdAt), 'MMMM do yyyy, h:mma')}</div>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</FormProvider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useFormFields } from '../components/forms/Form/context';
|
import { useFormFields } from '../components/forms/FormProvider/context';
|
||||||
|
|
||||||
const useTitle = (useAsTitle) => {
|
const useTitle = (useAsTitle) => {
|
||||||
const { getField } = useFormFields();
|
const { getField } = useFormFields();
|
||||||
|
|||||||
Reference in New Issue
Block a user