Compare commits
3 Commits
fix/json-p
...
v0.0.23
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9aaadfa115 | ||
|
|
fe25c1920a | ||
|
|
0f1ba6b315 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/payload",
|
||||
"version": "0.0.22",
|
||||
"version": "0.0.23",
|
||||
"description": "CMS and Application Framework",
|
||||
"license": "ISC",
|
||||
"author": "Payload CMS LLC",
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import config from 'payload/config';
|
||||
import Button from '../Button';
|
||||
import { useForm } from '../../forms/Form/context';
|
||||
import { useForm } from '../../forms/FormProvider/context';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useForm } from '../../forms/Form/context';
|
||||
import { useForm } from '../../forms/FormProvider/context';
|
||||
import { useUser } from '../../data/User';
|
||||
import Button from '../Button';
|
||||
|
||||
|
||||
@@ -1,281 +1,21 @@
|
||||
import React, {
|
||||
useReducer, useEffect, useRef, useState,
|
||||
} from 'react';
|
||||
import { objectToFormData } from 'object-to-formdata';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { unflatten } from 'flatley';
|
||||
import { useFormFields } from '../FormProvider/context';
|
||||
import HiddenInput from '../field-types/HiddenInput';
|
||||
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';
|
||||
|
||||
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 {
|
||||
disabled,
|
||||
onSubmit,
|
||||
ajax,
|
||||
method,
|
||||
action,
|
||||
handleResponse,
|
||||
onSuccess,
|
||||
children,
|
||||
className,
|
||||
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);
|
||||
}
|
||||
|
||||
// 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 { submit } = useFormFields();
|
||||
|
||||
const classes = [
|
||||
className,
|
||||
@@ -285,63 +25,28 @@ const Form = (props) => {
|
||||
return (
|
||||
<form
|
||||
noValidate
|
||||
onSubmit={contextRef.current.submit}
|
||||
method={method}
|
||||
action={action}
|
||||
onSubmit={submit}
|
||||
className={classes}
|
||||
>
|
||||
<FormContext.Provider value={contextRef.current}>
|
||||
<FieldContext.Provider value={{
|
||||
fields,
|
||||
...contextRef.current,
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
|
||||
<HiddenInput
|
||||
path="locale"
|
||||
defaultValue={locale}
|
||||
/>
|
||||
{children}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Form.defaultProps = {
|
||||
redirect: '',
|
||||
onSubmit: null,
|
||||
ajax: true,
|
||||
method: 'POST',
|
||||
action: '',
|
||||
handleResponse: null,
|
||||
onSuccess: null,
|
||||
className: '',
|
||||
disableSuccessStatus: false,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
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([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
]).isRequired,
|
||||
className: PropTypes.string,
|
||||
redirect: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Form;
|
||||
|
||||
@@ -5,6 +5,26 @@ function fieldReducer(state, action) {
|
||||
...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': {
|
||||
const newState = { ...state };
|
||||
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 PropTypes from 'prop-types';
|
||||
import { useFormProcessing } from '../Form/context';
|
||||
import { useFormProcessing } from '../FormProvider/context';
|
||||
import Button from '../../elements/Button';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -8,10 +8,11 @@ import Button from '../../../elements/Button';
|
||||
import DraggableSection from '../../DraggableSection';
|
||||
import reducer from '../rowReducer';
|
||||
import { useRenderedFields } from '../../RenderFields';
|
||||
import { useForm } from '../../Form/context';
|
||||
import { useForm } from '../../FormProvider/context';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import Error from '../../Error';
|
||||
import { array } from '../../../../../fields/validations';
|
||||
import SubForm from '../../SubForm';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -191,6 +192,7 @@ ArrayFieldType.defaultProps = {
|
||||
minRows: undefined,
|
||||
singularLabel: 'Row',
|
||||
permissions: {},
|
||||
path: '',
|
||||
};
|
||||
|
||||
ArrayFieldType.propTypes = {
|
||||
@@ -206,7 +208,7 @@ ArrayFieldType.propTypes = {
|
||||
label: PropTypes.string,
|
||||
singularLabel: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
path: PropTypes.string,
|
||||
fieldTypes: PropTypes.shape({}).isRequired,
|
||||
validate: PropTypes.func,
|
||||
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 CopyToClipboard from '../../../elements/CopyToClipboard';
|
||||
import { text } from '../../../../../fields/validations';
|
||||
import { useFormFields } from '../../Form/context';
|
||||
import { useFormFields } from '../../FormProvider/context';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import Password from '../Password';
|
||||
import Checkbox from '../Checkbox';
|
||||
import Button from '../../../elements/Button';
|
||||
import ConfirmPassword from '../ConfirmPassword';
|
||||
import { useFormFields } from '../../Form/context';
|
||||
import { useFormFields } from '../../FormProvider/context';
|
||||
import APIKey from './APIKey';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -8,14 +8,15 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
import withCondition from '../../withCondition';
|
||||
import Button from '../../../elements/Button';
|
||||
import reducer from '../rowReducer';
|
||||
import { useForm } from '../../Form/context';
|
||||
import { useForm } from '../../FormProvider/context';
|
||||
import DraggableSection from '../../DraggableSection';
|
||||
import { useRenderedFields } from '../../RenderFields';
|
||||
import Error from '../../Error';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import Popup from '../../../elements/Popup';
|
||||
import BlockSelector from './BlockSelector';
|
||||
import { blocks } from '../../../../../fields/validations';
|
||||
import { blocks as blockValidator } from '../../../../../fields/validations';
|
||||
import SubForm from '../../SubForm';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -242,7 +243,7 @@ Blocks.defaultProps = {
|
||||
defaultValue: [],
|
||||
initialData: [],
|
||||
singularLabel: 'Block',
|
||||
validate: blocks,
|
||||
validate: blockValidator,
|
||||
required: false,
|
||||
maxRows: 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 Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
import { useFormFields } from '../../Form/context';
|
||||
import { useFormFields } from '../../FormProvider/context';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import RenderFields, { useRenderedFields } from '../../RenderFields';
|
||||
import withCondition from '../../withCondition';
|
||||
import SubForm from '../../SubForm';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -47,4 +48,23 @@ Group.propTypes = {
|
||||
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 MinimalTemplate from '../../../../templates/Minimal';
|
||||
import Form from '../../../Form';
|
||||
import FormProvider from '../../../FormProvider';
|
||||
import Button from '../../../../elements/Button';
|
||||
import formatFields from '../../../../views/collections/Edit/formatFields';
|
||||
import RenderFields from '../../../RenderFields';
|
||||
@@ -45,34 +46,36 @@ const AddUploadModal = (props) => {
|
||||
slug={slug}
|
||||
>
|
||||
<MinimalTemplate width="wide">
|
||||
<Form
|
||||
<FormProvider
|
||||
method="post"
|
||||
action={`${serverURL}${api}/${collection.slug}`}
|
||||
onSuccess={onSuccess}
|
||||
disableSuccessStatus
|
||||
>
|
||||
<header className={`${baseClass}__header`}>
|
||||
<h1>
|
||||
New
|
||||
{' '}
|
||||
{collection.labels.singular}
|
||||
</h1>
|
||||
<FormSubmit>Save</FormSubmit>
|
||||
<Button
|
||||
icon="x"
|
||||
round
|
||||
buttonStyle="icon-label"
|
||||
iconStyle="with-border"
|
||||
onClick={closeAll}
|
||||
<Form>
|
||||
<header className={`${baseClass}__header`}>
|
||||
<h1>
|
||||
New
|
||||
{' '}
|
||||
{collection.labels.singular}
|
||||
</h1>
|
||||
<FormSubmit>Save</FormSubmit>
|
||||
<Button
|
||||
icon="x"
|
||||
round
|
||||
buttonStyle="icon-label"
|
||||
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>
|
||||
<RenderFields
|
||||
filter={(field) => (!field.position || (field.position && field.position !== 'sidebar'))}
|
||||
fieldTypes={fieldTypes}
|
||||
fieldSchema={fields}
|
||||
customComponentsPath={`${collection.slug}.fields.`}
|
||||
/>
|
||||
</Form>
|
||||
</Form>
|
||||
</FormProvider>
|
||||
</MinimalTemplate>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
useCallback, useEffect, useState,
|
||||
} from 'react';
|
||||
import { useFormProcessing, useFormSubmitted, useFormModified, useForm } from '../Form/context';
|
||||
import { useFormProcessing, useFormSubmitted, useFormModified, useForm } from '../FormProvider/context';
|
||||
import useDebounce from '../../../hooks/useDebounce';
|
||||
import useUnmountEffect from '../../../hooks/useUnmountEffect';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { useFormFields } from '../Form/context';
|
||||
import { useFormFields } from '../FormProvider/context';
|
||||
|
||||
const withCondition = (Field) => {
|
||||
const CheckForCondition = (props) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
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 Button from '../../elements/Button';
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import format from 'date-fns/format';
|
||||
import config from 'payload/config';
|
||||
import Eyebrow from '../../elements/Eyebrow';
|
||||
import Form from '../../forms/Form';
|
||||
import FormProvider from '../../forms/FormProvider';
|
||||
import PreviewButton from '../../elements/PreviewButton';
|
||||
import FormSubmit from '../../forms/Submit';
|
||||
import RenderFields from '../../forms/RenderFields';
|
||||
@@ -48,93 +49,95 @@ const DefaultAccount = (props) => {
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<Form
|
||||
<FormProvider
|
||||
className={`${baseClass}__form`}
|
||||
method="put"
|
||||
action={`${serverURL}${api}/${slug}/${data?.id}`}
|
||||
>
|
||||
<div className={`${baseClass}__main`}>
|
||||
<Eyebrow />
|
||||
<LeaveWithoutSaving />
|
||||
<div className={`${baseClass}__edit`}>
|
||||
<header className={`${baseClass}__header`}>
|
||||
<h1>
|
||||
<RenderTitle {...{ data, useAsTitle, fallback: '[Untitled]' }} />
|
||||
</h1>
|
||||
</header>
|
||||
<RenderFields
|
||||
filter={(field) => (!field.position || (field.position && field.position !== 'sidebar'))}
|
||||
fieldTypes={fieldTypes}
|
||||
fieldSchema={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}
|
||||
<Form>
|
||||
<div className={`${baseClass}__main`}>
|
||||
<Eyebrow />
|
||||
<LeaveWithoutSaving />
|
||||
<div className={`${baseClass}__edit`}>
|
||||
<header className={`${baseClass}__header`}>
|
||||
<h1>
|
||||
<RenderTitle {...{ data, useAsTitle, fallback: '[Untitled]' }} />
|
||||
</h1>
|
||||
</header>
|
||||
<RenderFields
|
||||
filter={(field) => (!field.position || (field.position && field.position !== 'sidebar'))}
|
||||
fieldTypes={fieldTypes}
|
||||
fieldSchema={fields}
|
||||
initialData={dataToRender}
|
||||
customComponentsPath={`${slug}.fields.`}
|
||||
/>
|
||||
</li>
|
||||
</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`}>
|
||||
<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
|
||||
filter={(field) => field.position === 'sidebar'}
|
||||
position="sidebar"
|
||||
fieldTypes={fieldTypes}
|
||||
fieldSchema={fields}
|
||||
initialData={dataToRender}
|
||||
customComponentsPath={`${slug}.fields.`}
|
||||
/>
|
||||
</div>
|
||||
<ul className={`${baseClass}__meta`}>
|
||||
<li>
|
||||
<div className={`${baseClass}__label`}>ID</div>
|
||||
<div>{data?.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>
|
||||
)}
|
||||
<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>
|
||||
</ul>
|
||||
<div className={`${baseClass}__document-actions${preview ? ` ${baseClass}__document-actions--with-preview` : ''}`}>
|
||||
<PreviewButton generatePreviewURL={preview} />
|
||||
<FormSubmit>Save</FormSubmit>
|
||||
</div>
|
||||
<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
|
||||
filter={(field) => field.position === 'sidebar'}
|
||||
position="sidebar"
|
||||
fieldTypes={fieldTypes}
|
||||
fieldSchema={fields}
|
||||
initialData={dataToRender}
|
||||
customComponentsPath={`${slug}.fields.`}
|
||||
/>
|
||||
</div>
|
||||
<ul className={`${baseClass}__meta`}>
|
||||
<li>
|
||||
<div className={`${baseClass}__label`}>ID</div>
|
||||
<div>{data?.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>
|
||||
</div>
|
||||
</Form>
|
||||
</ul>
|
||||
</div>
|
||||
</Form>
|
||||
</FormProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -163,6 +166,7 @@ DefaultAccount.propTypes = {
|
||||
data: PropTypes.shape({
|
||||
updatedAt: PropTypes.string,
|
||||
createdAt: PropTypes.string,
|
||||
id: PropTypes.string,
|
||||
}),
|
||||
onSave: PropTypes.func,
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import config from 'payload/config';
|
||||
import MinimalTemplate from '../../templates/Minimal';
|
||||
import FormProvider from '../../forms/FormProvider';
|
||||
import Form from '../../forms/Form';
|
||||
import RenderFields from '../../forms/RenderFields';
|
||||
import * as fieldTypes from '../../forms/field-types';
|
||||
@@ -48,21 +49,23 @@ const CreateFirstUser = (props) => {
|
||||
<MinimalTemplate className={baseClass}>
|
||||
<h1>Welcome to Payload</h1>
|
||||
<p>To begin, create your first user.</p>
|
||||
<Form
|
||||
<FormProvider
|
||||
onSuccess={onSuccess}
|
||||
method="POST"
|
||||
redirect={admin}
|
||||
action={`${serverURL}${api}/${userSlug}/first-register`}
|
||||
>
|
||||
<RenderFields
|
||||
fieldSchema={[
|
||||
...fields,
|
||||
...userConfig.fields,
|
||||
]}
|
||||
fieldTypes={fieldTypes}
|
||||
/>
|
||||
<FormSubmit>Create</FormSubmit>
|
||||
</Form>
|
||||
<Form>
|
||||
<RenderFields
|
||||
fieldSchema={[
|
||||
...fields,
|
||||
...userConfig.fields,
|
||||
]}
|
||||
fieldTypes={fieldTypes}
|
||||
/>
|
||||
<FormSubmit>Create</FormSubmit>
|
||||
</Form>
|
||||
</FormProvider>
|
||||
</MinimalTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Link } from 'react-router-dom';
|
||||
import config from 'payload/config';
|
||||
import MinimalTemplate from '../../templates/Minimal';
|
||||
import StatusList, { useStatusList } from '../../elements/Status';
|
||||
import FormProvider from '../../forms/FormProvider';
|
||||
import Form from '../../forms/Form';
|
||||
import Email from '../../forms/field-types/Email';
|
||||
import FormSubmit from '../../forms/Submit';
|
||||
@@ -84,21 +85,22 @@ const ForgotPassword = () => {
|
||||
return (
|
||||
<MinimalTemplate className={baseClass}>
|
||||
<StatusList />
|
||||
<Form
|
||||
novalidate
|
||||
<FormProvider
|
||||
handleResponse={handleResponse}
|
||||
method="POST"
|
||||
action={`${serverURL}${api}/${userSlug}/forgot-password`}
|
||||
>
|
||||
<h1>Forgot Password</h1>
|
||||
<p>Please enter your email below. You will receive an email message with instructions on how to reset your password.</p>
|
||||
<Email
|
||||
label="Email Address"
|
||||
name="email"
|
||||
required
|
||||
/>
|
||||
<FormSubmit>Submit</FormSubmit>
|
||||
</Form>
|
||||
<Form>
|
||||
<h1>Forgot Password</h1>
|
||||
<p>Please enter your email below. You will receive an email message with instructions on how to reset your password.</p>
|
||||
<Email
|
||||
label="Email Address"
|
||||
name="email"
|
||||
required
|
||||
/>
|
||||
<FormSubmit>Submit</FormSubmit>
|
||||
</Form>
|
||||
</FormProvider>
|
||||
<Link to={`${admin}/login`}>Back to login</Link>
|
||||
</MinimalTemplate>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import format from 'date-fns/format';
|
||||
import config from 'payload/config';
|
||||
import Eyebrow from '../../elements/Eyebrow';
|
||||
import FormProvider from '../../forms/FormProvider';
|
||||
import Form from '../../forms/Form';
|
||||
import PreviewButton from '../../elements/PreviewButton';
|
||||
import FormSubmit from '../../forms/Submit';
|
||||
@@ -35,84 +36,85 @@ const DefaultGlobalView = (props) => {
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<Form
|
||||
className={`${baseClass}__form`}
|
||||
<FormProvider
|
||||
method="post"
|
||||
action={action}
|
||||
onSuccess={onSave}
|
||||
disabled={!hasSavePermission}
|
||||
>
|
||||
<div className={`${baseClass}__main`}>
|
||||
<Eyebrow />
|
||||
<LeaveWithoutSaving />
|
||||
<div className={`${baseClass}__edit`}>
|
||||
<header className={`${baseClass}__header`}>
|
||||
<h1>
|
||||
Edit
|
||||
{' '}
|
||||
{label}
|
||||
</h1>
|
||||
</header>
|
||||
<RenderFields
|
||||
operation="update"
|
||||
readOnly={!hasSavePermission}
|
||||
permissions={permissions.fields}
|
||||
filter={field => (!field.position || (field.position && field.position !== 'sidebar'))}
|
||||
fieldTypes={fieldTypes}
|
||||
fieldSchema={fields}
|
||||
initialData={data}
|
||||
customComponentsPath={`${slug}.fields.`}
|
||||
/>
|
||||
<Form className={`${baseClass}__form`}>
|
||||
<div className={`${baseClass}__main`}>
|
||||
<Eyebrow />
|
||||
<LeaveWithoutSaving />
|
||||
<div className={`${baseClass}__edit`}>
|
||||
<header className={`${baseClass}__header`}>
|
||||
<h1>
|
||||
Edit
|
||||
{' '}
|
||||
{label}
|
||||
</h1>
|
||||
</header>
|
||||
<RenderFields
|
||||
operation="update"
|
||||
readOnly={!hasSavePermission}
|
||||
permissions={permissions.fields}
|
||||
filter={(field) => (!field.position || (field.position && field.position !== 'sidebar'))}
|
||||
fieldTypes={fieldTypes}
|
||||
fieldSchema={fields}
|
||||
initialData={data}
|
||||
customComponentsPath={`${slug}.fields.`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${baseClass}__sidebar`}>
|
||||
<div className={`${baseClass}__document-actions${preview ? ` ${baseClass}__document-actions--with-preview` : ''}`}>
|
||||
<PreviewButton generatePreviewURL={preview} />
|
||||
{hasSavePermission && (
|
||||
<FormSubmit>Save</FormSubmit>
|
||||
<div className={`${baseClass}__sidebar`}>
|
||||
<div className={`${baseClass}__document-actions${preview ? ` ${baseClass}__document-actions--with-preview` : ''}`}>
|
||||
<PreviewButton generatePreviewURL={preview} />
|
||||
{hasSavePermission && (
|
||||
<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>
|
||||
{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>
|
||||
</Form>
|
||||
</Form>
|
||||
</FormProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Link, useHistory } from 'react-router-dom';
|
||||
import config from 'payload/config';
|
||||
import Logo from '../../graphics/Logo';
|
||||
import MinimalTemplate from '../../templates/Minimal';
|
||||
import FormProvider from '../../forms/FormProvider';
|
||||
import Form from '../../forms/Form';
|
||||
import Email from '../../forms/field-types/Email';
|
||||
import Password from '../../forms/field-types/Password';
|
||||
@@ -57,29 +58,31 @@ const Login = () => {
|
||||
<div className={`${baseClass}__brand`}>
|
||||
<Logo />
|
||||
</div>
|
||||
<Form
|
||||
<FormProvider
|
||||
disableSuccessStatus
|
||||
onSuccess={onSuccess}
|
||||
method="POST"
|
||||
action={`${serverURL}${api}/${userSlug}/login`}
|
||||
>
|
||||
<Email
|
||||
label="Email Address"
|
||||
name="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
/>
|
||||
<Password
|
||||
error="password"
|
||||
label="Password"
|
||||
name="password"
|
||||
required
|
||||
/>
|
||||
<Link to={`${admin}/forgot`}>
|
||||
Forgot password?
|
||||
</Link>
|
||||
<FormSubmit>Login</FormSubmit>
|
||||
</Form>
|
||||
<Form>
|
||||
<Email
|
||||
label="Email Address"
|
||||
name="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
/>
|
||||
<Password
|
||||
error="password"
|
||||
label="Password"
|
||||
name="password"
|
||||
required
|
||||
/>
|
||||
<Link to={`${admin}/forgot`}>
|
||||
Forgot password?
|
||||
</Link>
|
||||
<FormSubmit>Login</FormSubmit>
|
||||
</Form>
|
||||
</FormProvider>
|
||||
</MinimalTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { Link, useHistory, useParams } from 'react-router-dom';
|
||||
import config from 'payload/config';
|
||||
import StatusList from '../../elements/Status';
|
||||
import FormProvider from '../../forms/FormProvider';
|
||||
import Form from '../../forms/Form';
|
||||
import Password from '../../forms/field-types/Password';
|
||||
import FormSubmit from '../../forms/Submit';
|
||||
@@ -59,24 +60,26 @@ const ResetPassword = () => {
|
||||
<div className={baseClass}>
|
||||
<div className={`${baseClass}__wrap`}>
|
||||
<StatusList />
|
||||
<Form
|
||||
<FormProvider
|
||||
handleResponse={handleResponse}
|
||||
method="POST"
|
||||
action={`${serverURL}${api}/${userSlug}/reset-password`}
|
||||
redirect={admin}
|
||||
>
|
||||
<Password
|
||||
error="password"
|
||||
label="Password"
|
||||
name="password"
|
||||
required
|
||||
/>
|
||||
<HiddenInput
|
||||
name="token"
|
||||
defaultValue={token}
|
||||
/>
|
||||
<FormSubmit>Reset Password</FormSubmit>
|
||||
</Form>
|
||||
<Form>
|
||||
<Password
|
||||
error="password"
|
||||
label="Password"
|
||||
name="password"
|
||||
required
|
||||
/>
|
||||
<HiddenInput
|
||||
name="token"
|
||||
defaultValue={token}
|
||||
/>
|
||||
<FormSubmit>Reset Password</FormSubmit>
|
||||
</Form>
|
||||
</FormProvider>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Link, useRouteMatch } from 'react-router-dom';
|
||||
import format from 'date-fns/format';
|
||||
import config from 'payload/config';
|
||||
import Eyebrow from '../../../elements/Eyebrow';
|
||||
import FormProvider from '../../../forms/FormProvider';
|
||||
import Form from '../../../forms/Form';
|
||||
import Loading from '../../../elements/Loading';
|
||||
import PreviewButton from '../../../elements/PreviewButton';
|
||||
@@ -55,130 +56,131 @@ const DefaultEditView = (props) => {
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<Form
|
||||
className={`${baseClass}__form`}
|
||||
<FormProvider
|
||||
method={id ? 'put' : 'post'}
|
||||
action={action}
|
||||
onSuccess={onSave}
|
||||
disabled={!hasSavePermission}
|
||||
>
|
||||
<div className={`${baseClass}__main`}>
|
||||
<Eyebrow />
|
||||
<LeaveWithoutSaving />
|
||||
<div className={`${baseClass}__edit`}>
|
||||
{isLoading && (
|
||||
<Loading />
|
||||
)}
|
||||
{!isLoading && (
|
||||
<React.Fragment>
|
||||
<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 && (
|
||||
<Form className={`${baseClass}__form`}>
|
||||
<div className={`${baseClass}__main`}>
|
||||
<Eyebrow />
|
||||
<LeaveWithoutSaving />
|
||||
<div className={`${baseClass}__edit`}>
|
||||
{isLoading && (
|
||||
<Loading />
|
||||
)}
|
||||
{!isLoading && (
|
||||
<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`}>
|
||||
<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 === 'sidebar'}
|
||||
position="sidebar"
|
||||
filter={(field) => (!field?.admin?.position || (field?.admin?.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>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
<div className={`${baseClass}__sidebar`}>
|
||||
{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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useFormFields } from '../components/forms/Form/context';
|
||||
import { useFormFields } from '../components/forms/FormProvider/context';
|
||||
|
||||
const useTitle = (useAsTitle) => {
|
||||
const { getField } = useFormFields();
|
||||
|
||||
Reference in New Issue
Block a user