diff --git a/demo/collections/Admin.js b/demo/collections/Admin.js
index d0b5679b1b..4f6b2d631e 100644
--- a/demo/collections/Admin.js
+++ b/demo/collections/Admin.js
@@ -21,7 +21,7 @@ module.exports = {
admin: () => true,
},
auth: {
- tokenExpiration: 300,
+ tokenExpiration: 7200,
useAPIKey: true,
secureCookie: process.env.NODE_ENV === 'production',
},
diff --git a/demo/globals/GlobalWithPolicies.js b/demo/globals/GlobalWithPolicies.js
index a7c721989a..ad43109be8 100644
--- a/demo/globals/GlobalWithPolicies.js
+++ b/demo/globals/GlobalWithPolicies.js
@@ -12,7 +12,6 @@ module.exports = {
name: 'title',
label: 'Site Title',
type: 'text',
- localized: true,
maxLength: 100,
required: true,
},
diff --git a/src/client/components/views/Global/Default.js b/src/client/components/views/Global/Default.js
new file mode 100644
index 0000000000..ff61fa946a
--- /dev/null
+++ b/src/client/components/views/Global/Default.js
@@ -0,0 +1,147 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import format from 'date-fns/format';
+import config from 'payload/config';
+import Eyebrow from '../../elements/Eyebrow';
+import Form from '../../forms/Form';
+import PreviewButton from '../../elements/PreviewButton';
+import FormSubmit from '../../forms/Submit';
+import RenderFields from '../../forms/RenderFields';
+import CopyToClipboard from '../../elements/CopyToClipboard';
+import * as fieldTypes from '../../forms/field-types';
+import LeaveWithoutSaving from '../../modals/LeaveWithoutSaving';
+
+import './index.scss';
+
+const { serverURL, routes: { api } } = config;
+
+const baseClass = 'global-edit';
+
+const DefaultGlobalView = (props) => {
+ const {
+ global, data, onSave, permissions,
+ } = props;
+
+ const {
+ slug,
+ fields,
+ preview,
+ label,
+ } = global;
+
+ const apiURL = `${serverURL}${api}/globals/${slug}`;
+ const action = `${serverURL}${api}/globals/${slug}`;
+ const hasSavePermission = permissions?.update?.permission;
+
+ return (
+
+ );
+};
+
+DefaultGlobalView.defaultProps = {
+ data: undefined,
+};
+
+DefaultGlobalView.propTypes = {
+ global: PropTypes.shape({
+ label: PropTypes.string.isRequired,
+ slug: PropTypes.string,
+ fields: PropTypes.arrayOf(PropTypes.shape({})),
+ preview: PropTypes.func,
+ }).isRequired,
+ data: PropTypes.shape({
+ updatedAt: PropTypes.string,
+ }),
+ onSave: PropTypes.func.isRequired,
+ permissions: PropTypes.shape({
+ update: PropTypes.shape({
+ permission: PropTypes.bool,
+ }),
+ fields: PropTypes.shape({}),
+ }).isRequired,
+};
+
+export default DefaultGlobalView;
diff --git a/src/client/components/views/Global/index.js b/src/client/components/views/Global/index.js
index 5c372647dc..73a1bcdff7 100644
--- a/src/client/components/views/Global/index.js
+++ b/src/client/components/views/Global/index.js
@@ -1,68 +1,76 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
+import { useHistory, useLocation } from 'react-router-dom';
import config from 'payload/config';
import { useStepNav } from '../../elements/StepNav';
import usePayloadAPI from '../../../hooks/usePayloadAPI';
-import Form from '../../forms/Form';
-import RenderFields from '../../forms/RenderFields';
-import * as fieldTypes from '../../forms/field-types';
+import { useUser } from '../../data/User';
-const {
- serverURL,
- routes: {
- admin,
- api,
- },
-} = config;
+import RenderCustomComponent from '../../utilities/RenderCustomComponent';
+import DefaultGlobal from './Default';
-const baseClass = 'global-edit';
+const { serverURL, routes: { admin, api } } = config;
-const Global = (props) => {
- const { global: { slug, label, fields } } = props;
+const GlobalView = (props) => {
+ const { state: locationState } = useLocation();
+ const history = useHistory();
const { setStepNav } = useStepNav();
+ const { permissions } = useUser();
+
+ const { global } = props;
+
+ const {
+ slug,
+ label,
+ } = global;
+
+ const onSave = (json) => {
+ history.push(`${admin}/globals/${global.slug}`, {
+ status: {
+ message: json.message,
+ type: 'success',
+ },
+ data: json.doc,
+ });
+ };
const [{ data }] = usePayloadAPI(
`${serverURL}${api}/globals/${slug}`,
{ initialParams: { 'fallback-locale': 'null' } },
);
+ const dataToRender = locationState?.data || data;
+
useEffect(() => {
- setStepNav([{
- url: `${admin}/globals/${slug}`,
+ const nav = [{
label,
- }]);
- }, [setStepNav, slug, label]);
+ }];
+
+ setStepNav(nav);
+ }, [setStepNav, label]);
+
+ const globalPermissions = permissions?.[slug];
return (
-
-
-
- Edit
- {' '}
- {label}
-
-
-
-
+
);
};
-Global.propTypes = {
+GlobalView.propTypes = {
global: PropTypes.shape({
- label: PropTypes.string,
- slug: PropTypes.string,
+ label: PropTypes.string.isRequired,
+ slug: PropTypes.string.isRequired,
fields: PropTypes.arrayOf(PropTypes.shape({})),
}).isRequired,
};
-export default Global;
+export default GlobalView;
diff --git a/src/client/components/views/Global/index.scss b/src/client/components/views/Global/index.scss
new file mode 100644
index 0000000000..495816a66c
--- /dev/null
+++ b/src/client/components/views/Global/index.scss
@@ -0,0 +1,186 @@
+@import '../../../scss/styles.scss';
+
+.global-edit {
+ width: 100%;
+
+ &__form {
+ display: flex;
+ align-items: stretch;
+ }
+
+ &__main {
+ min-width: 0;
+ }
+
+ &__header {
+ h1 {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+
+ &__collection-actions {
+ list-style: none;
+ margin: 0;
+ padding: base(1.5) 0 base(.5);
+ display: flex;
+
+ li {
+ margin-right: base(.75);
+ }
+ }
+
+ &__edit {
+ background: white;
+ padding: base(5) base(6) base(6);
+ }
+
+ &__sidebar {
+ padding-top: base(3);
+ padding-bottom: base(1);
+ width: base(22);
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ }
+
+ &__collection-actions,
+ &__document-actions,
+ &__meta,
+ &__sidebar-fields,
+ &__api-url {
+ padding-left: base(1.5);
+ }
+
+ &__document-actions {
+ @include blur-bg;
+ position: sticky;
+ top: 0;
+ z-index: 2;
+ padding-right: $baseline;
+ }
+
+ &__document-actions--with-preview {
+ display: flex;
+
+ > * {
+ width: calc(50% - #{base(.5)});
+ }
+
+ > *:first-child {
+ margin-right: base(.5);
+ }
+
+ > *:last-child {
+ margin-left: base(.5);
+ }
+
+ .form-submit {
+ .btn {
+ width: 100%;
+ padding-left: base(2);
+ padding-right: base(2);
+ }
+ }
+ }
+
+ &__api-url {
+ margin-bottom: base(1.5);
+ padding-right: base(1.5);
+
+ a {
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+
+ &__meta {
+ margin: 0;
+ padding-top: $baseline;
+ list-style: none;
+
+ li {
+ margin-bottom: base(.5);
+ }
+ }
+
+ &__label {
+ color: $color-gray;
+ }
+
+ &__collection-actions,
+ &__api-url {
+ a, button {
+ cursor: pointer;
+ font-weight: 600;
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ }
+
+ &--is-editing {
+ .collection-edit__sidebar {
+ padding-top: 0;
+ }
+ }
+
+ @include large-break {
+ &__edit {
+ padding: base(3.5);
+ }
+ }
+
+ @include mid-break {
+ &__sidebar {
+ width: unset;
+ }
+
+ &__form {
+ display: block;
+ }
+
+ &__edit {
+ padding: $baseline;
+ margin-bottom: $baseline;
+ }
+
+ &__document-actions {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ top: auto;
+ }
+
+ &__document-actions,
+ &__meta,
+ &__sidebar-fields,
+ &__api-url {
+ padding-left: $baseline;
+ }
+
+ &__api-url {
+ margin-bottom: base(.5);
+ }
+
+ &__collection-actions {
+ margin-top: base(.5);
+ padding-left: $baseline;
+ padding-bottom: 0;
+ order: 1;
+
+ li {
+ margin: 0 base(.5) 0 0;
+ }
+ }
+
+ &__sidebar {
+ padding-bottom: base(4);
+ }
+ }
+}
diff --git a/src/client/components/views/collections/Edit/Default.js b/src/client/components/views/collections/Edit/Default.js
index d863c9934b..7ebdcc01f3 100644
--- a/src/client/components/views/collections/Edit/Default.js
+++ b/src/client/components/views/collections/Edit/Default.js
@@ -39,7 +39,7 @@ const DefaultEditView = (props) => {
const apiURL = `${serverURL}${api}/${slug}/${id}`;
let action = `${serverURL}${api}/${slug}${isEditing ? `/${id}` : ''}`;
- const hasSavePermission = (isEditing && permissions?.update?.permission) || (!isEditing && permissions?.update?.permission);
+ const hasSavePermission = (isEditing && permissions?.update?.permission) || (!isEditing && permissions?.create?.permission);
if (auth && !isEditing) {
action = `${action}/register`;
@@ -170,7 +170,6 @@ const DefaultEditView = (props) => {
DefaultEditView.defaultProps = {
isEditing: false,
data: undefined,
- onSave: null,
};
DefaultEditView.propTypes = {
@@ -191,7 +190,7 @@ DefaultEditView.propTypes = {
updatedAt: PropTypes.string,
createdAt: PropTypes.string,
}),
- onSave: PropTypes.func,
+ onSave: PropTypes.func.isRequired,
permissions: PropTypes.shape({
create: PropTypes.shape({
permission: PropTypes.bool,
diff --git a/src/collections/operations/update.js b/src/collections/operations/update.js
index abe5be00c5..eed1b31876 100644
--- a/src/collections/operations/update.js
+++ b/src/collections/operations/update.js
@@ -154,7 +154,7 @@ const update = async (args) => {
}
// /////////////////////////////////////
- // 7. Return updated document
+ // 9. Return updated document
// /////////////////////////////////////
return doc;
diff --git a/src/globals/buildModel.js b/src/globals/buildModel.js
index ae6e69e30f..6b6ade840e 100644
--- a/src/globals/buildModel.js
+++ b/src/globals/buildModel.js
@@ -1,6 +1,11 @@
const mongoose = require('mongoose');
const autopopulate = require('mongoose-autopopulate');
-const mongooseHidden = require('mongoose-hidden');
+const mongooseHidden = require('mongoose-hidden')({
+ hidden: {
+ _id: true, __v: true,
+ },
+ applyRecursively: true,
+});
const buildSchema = require('../mongoose/buildSchema');
const localizationPlugin = require('../localization/plugin');
@@ -9,18 +14,12 @@ const buildModel = (config) => {
const globalsSchema = new mongoose.Schema({}, { discriminatorKey: 'globalType', timestamps: false })
.plugin(localizationPlugin, config.localization)
.plugin(autopopulate)
- .plugin(mongooseHidden());
+ .plugin(mongooseHidden);
const Globals = mongoose.model('globals', globalsSchema);
Object.values(config.globals).forEach((globalConfig) => {
const globalSchema = buildSchema(globalConfig.fields);
-
- globalSchema
- .plugin(localizationPlugin, config.localization)
- .plugin(autopopulate)
- .plugin(mongooseHidden());
-
Globals.discriminator(globalConfig.slug, globalSchema);
});
diff --git a/src/globals/init.js b/src/globals/init.js
index 397cde7849..a1bd492187 100644
--- a/src/globals/init.js
+++ b/src/globals/init.js
@@ -1,5 +1,4 @@
const buildModel = require('./buildModel');
-const sanitize = require('./sanitize');
const routes = require('./routes');
function initGlobals() {
diff --git a/src/globals/operations/findOne.js b/src/globals/operations/findOne.js
index 5cd38360f1..e2f1630de7 100644
--- a/src/globals/operations/findOne.js
+++ b/src/globals/operations/findOne.js
@@ -55,14 +55,18 @@ const findOne = async (args) => {
}
let result = await Model.findOne({ globalType: slug });
+ let data = {};
- if (!result) throw new NotFound();
+ if (!result) {
+ result = {};
+ } else {
+ if (locale && result.setLocale) {
+ result.setLocale(locale, fallbackLocale);
+ }
- if (locale && result.setLocale) {
- result.setLocale(locale, fallbackLocale);
+ data = result.toJSON({ virtuals: true });
}
- let data = result.toJSON({ virtuals: true });
// /////////////////////////////////////
// 4. Execute field-level hooks and policies
diff --git a/src/globals/operations/update.js b/src/globals/operations/update.js
index 17326245a1..c63bacaa85 100644
--- a/src/globals/operations/update.js
+++ b/src/globals/operations/update.js
@@ -29,7 +29,7 @@ const update = async (args) => {
let global = await Model.findOne({ globalType: slug });
if (!global) {
- global = new Model();
+ global = new Model({ globalType: slug });
}
if (locale && global.setLocale) {
@@ -39,7 +39,7 @@ const update = async (args) => {
const globalJSON = global.toJSON({ virtuals: true });
// /////////////////////////////////////
- // 2. Execute before global hook
+ // 3. Execute before global hook
// /////////////////////////////////////
const { beforeUpdate } = args.config.hooks;
@@ -49,37 +49,37 @@ const update = async (args) => {
}
// /////////////////////////////////////
- // 3. Merge updates into existing data
+ // 4. Merge updates into existing data
// /////////////////////////////////////
options.data = deepmerge(globalJSON, options.data, { arrayMerge: overwriteMerge });
// /////////////////////////////////////
- // 4. Execute field-level hooks, policies, and validation
+ // 5. Execute field-level hooks, policies, and validation
// /////////////////////////////////////
options.data = await performFieldOperations(args.config, { ...options, hook: 'beforeUpdate', operationName: 'update' });
// /////////////////////////////////////
- // 4. Perform database operation
+ // 6. Perform database operation
// /////////////////////////////////////
- Object.assign(global, { ...options.data, globalType: slug });
+ Object.assign(global, options.data);
- global.save();
+ await global.save();
global = global.toJSON({ virtuals: true });
// /////////////////////////////////////
- // 5. Execute field-level hooks and policies
+ // 7. Execute field-level hooks and policies
// /////////////////////////////////////
- global = performFieldOperations(args.config, {
+ global = await performFieldOperations(args.config, {
...options, data: global, hook: 'afterRead', operationName: 'read',
});
// /////////////////////////////////////
- // 6. Execute after global hook
+ // 8. Execute after global hook
// /////////////////////////////////////
const { afterUpdate } = args.config.hooks;
@@ -89,7 +89,7 @@ const update = async (args) => {
}
// /////////////////////////////////////
- // 7. Return global
+ // 9. Return global
// /////////////////////////////////////
return global;
diff --git a/src/globals/requestHandlers/findOne.js b/src/globals/requestHandlers/findOne.js
index 932ae2456e..2b6fb6ee10 100644
--- a/src/globals/requestHandlers/findOne.js
+++ b/src/globals/requestHandlers/findOne.js
@@ -1,8 +1,7 @@
const httpStatus = require('http-status');
-const formatErrorResponse = require('../../express/responses/formatError');
const { findOne } = require('../operations');
-const findOneHandler = (Model, config) => async (req, res) => {
+const findOneHandler = (Model, config) => async (req, res, next) => {
try {
const { slug } = config;
@@ -16,7 +15,7 @@ const findOneHandler = (Model, config) => async (req, res) => {
return res.status(httpStatus.OK).json(result);
} catch (error) {
- return res.status(error.status || httpStatus.INTERNAL_SERVER_ERROR).json(formatErrorResponse(error));
+ return next(error);
}
};
diff --git a/src/globals/requestHandlers/update.js b/src/globals/requestHandlers/update.js
index 675a7b67e1..f40b455a7d 100644
--- a/src/globals/requestHandlers/update.js
+++ b/src/globals/requestHandlers/update.js
@@ -1,8 +1,7 @@
const httpStatus = require('http-status');
-const formatErrorResponse = require('../../express/responses/formatError');
const { update } = require('../operations');
-const updateHandler = (Model, config) => async (req, res) => {
+const updateHandler = (Model, config) => async (req, res, next) => {
try {
const { slug } = config;
@@ -12,11 +11,12 @@ const updateHandler = (Model, config) => async (req, res) => {
config,
slug,
depth: req.query.depth,
+ data: req.body,
});
return res.status(httpStatus.OK).json({ message: 'Global saved successfully.', result });
} catch (error) {
- return res.status(error.status || httpStatus.INTERNAL_SERVER_ERROR).json(formatErrorResponse(error));
+ return next(error);
}
};
diff --git a/src/globals/routes.js b/src/globals/routes.js
index c242f8c807..ec2016bac5 100644
--- a/src/globals/routes.js
+++ b/src/globals/routes.js
@@ -10,8 +10,7 @@ const registerGlobals = (globalConfigs, Globals) => {
router
.route(`/globals/${global.slug}`)
.get(findOne(Globals, global))
- .post(update(Globals, global))
- .put(update(Globals, global));
+ .post(update(Globals, global));
});
return router;
diff --git a/src/utilities/getPropSubset.js b/src/utilities/getPropSubset.js
deleted file mode 100644
index 07635e9650..0000000000
--- a/src/utilities/getPropSubset.js
+++ /dev/null
@@ -1 +0,0 @@
-module.exports = (keys, obj) => keys.reduce((a, c) => ({ ...a, [c]: obj[c] }), {});
diff --git a/src/utilities/secureConfig.js b/src/utilities/secureConfig.js
deleted file mode 100644
index b0f4b0d4de..0000000000
--- a/src/utilities/secureConfig.js
+++ /dev/null
@@ -1,33 +0,0 @@
-const deepCopyObject = require('../utilities/deepCopyObject');
-
-const recursivelySecure = (object) => {
- const newObject = deepCopyObject(object);
-
- delete newObject.hooks;
- delete newObject.components;
- delete newObject.policies;
-
- if (newObject.fields) {
- newObject.fields.forEach((field, i) => {
- newObject.fields[i] = recursivelySecure(field);
- });
- }
-
- return newObject;
-};
-
-const secureConfig = (insecureConfig) => {
- const config = deepCopyObject(insecureConfig);
-
- delete config.secret;
-
- recursivelySecure(config);
-
- config.collections.forEach((collection, i) => {
- config.collections[i] = recursivelySecure(collection);
- });
-
- return config;
-};
-
-module.exports = secureConfig;