init of the sanitize config rework and custom component config setup

This commit is contained in:
Jarrod Flesch
2020-02-27 16:38:15 -05:00
parent 147d6aac1c
commit d800097b38
24 changed files with 189 additions and 176 deletions

View File

@@ -1,9 +1,8 @@
import React, { useState, useEffect, Fragment } from 'react';
import React, { useState, useEffect } from 'react';
import {
Route, Switch, withRouter, Redirect,
} from 'react-router-dom';
import customComponents from 'payload-custom-components';
import getSanitizedConfig from '../config/getSanitizedConfig';
import getSanitizedClientConfig from '../config/getSanitizedClientConfig';
import { useUser } from './data/User';
import Dashboard from './views/Dashboard';
import Login from './views/Login';
@@ -16,11 +15,11 @@ import List from './views/collections/List';
import EditGlobal from './views/globals/Edit';
import { requests } from '../api';
const config = getSanitizedConfig();
const config = getSanitizedClientConfig();
const RenderCollectionRoutes = ({ match }) => {
const CollectionRoutes = ({ match }) => {
const collectionRoutes = config?.collections.reduce((routesToRender, collection) => {
const ListComponent = customComponents?.collections?.[collection.slug]?.views?.List || List;
const ListComponent = List;
routesToRender.push({
path: `${match.url}/collections/${collection.slug}`,
component: ListComponent,
@@ -127,7 +126,7 @@ const Routes = () => {
<Dashboard />
</Route>
<RenderCollectionRoutes match={match} />
<CollectionRoutes match={match} />
{config.globals && config.globals.map((global) => {
return (

View File

@@ -4,7 +4,7 @@ import Cookies from 'universal-cookie';
import some from 'async-some';
import ReactSelect from '../../../modules/ReactSelect';
import useFieldType from '../../useFieldType';
import getSanitizedConfig from '../../../../config/getSanitizedConfig';
import getSanitizedClientConfig from '../../../../config/getSanitizedClientConfig';
import Label from '../../Label';
import Error from '../../Error';
@@ -12,7 +12,7 @@ import './index.scss';
const cookies = new Cookies();
const { serverURL, collections } = getSanitizedConfig();
const { serverURL, collections } = getSanitizedClientConfig();
const defaultError = 'Please make a selection.';
const defaultValidate = value => value.length > 0;
@@ -49,8 +49,8 @@ class Relationship extends Component {
some(relationsToSearch, async (relation, callback) => {
const response = await fetch(`${serverURL}/${relation}?limit=${maxResultsPerRequest}&page=${lastLoadedPage}`, {
headers: {
Authorization: `Bearer ${token}`
}
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
@@ -59,7 +59,7 @@ class Relationship extends Component {
return callback(false, {
data,
relation,
})
});
}
callback({ relation, data });
@@ -73,10 +73,9 @@ class Relationship extends Component {
this.setState({
lastFullyLoadedRelation: relations.indexOf(relation),
lastLoadedPage: 1,
})
});
}
})
});
}
}
@@ -131,10 +130,10 @@ class Relationship extends Component {
this.setState({
options: [
...options,
...data.docs.map((doc) => ({
...data.docs.map(doc => ({
label: doc[collection.useAsTitle],
value: doc.id,
}))
})),
],
});
} else {
@@ -147,9 +146,9 @@ class Relationship extends Component {
value: {
relationTo: collection.slug,
value: doc.id,
}
}
})
},
};
});
if (optionsToAddTo) {
optionsToAddTo.options = [
@@ -167,7 +166,7 @@ class Relationship extends Component {
options: [
...allOptionGroups,
],
})
});
}
this.setState({
@@ -177,8 +176,8 @@ class Relationship extends Component {
handleInputChange = (search) => {
this.setState({
search
})
search,
});
}
handleMenuScrollToBottom = () => {
@@ -213,9 +212,9 @@ class Relationship extends Component {
const valueToRender = this.findValueInOptions(options, value);
/////////////////////////////////////////////
// ///////////////////////////////////////////
// TODO: simplify formatValue pattern seen below with react select
/////////////////////////////////////////////
// ///////////////////////////////////////////
return (
<div
@@ -306,23 +305,23 @@ const RelationshipFieldType = (props) => {
if (hasMultipleRelations) {
return {
...valueToFormat,
value: valueToFormat.value.id
value: valueToFormat.value.id,
};
}
return valueToFormat.id;
}
};
if (defaultValue) {
if (hasMany && Array.isArray(defaultValue)) {
let formattedDefaultValue = [];
const formattedDefaultValue = [];
defaultValue.forEach((individualValue) => {
formattedDefaultValue.push(formatDefaultValue(individualValue));
})
});
setFormattedDefaultValue(formattedDefaultValue);
} else {
setFormattedDefaultValue(formatDefaultValue(defaultValue));
};
}
}
}, [defaultValue]);

View File

@@ -1,6 +1,6 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import Sidebar from '../Sidebar';
import Sidebar from 'payload/Sidebar';
import StepNav, { useStepNav, StepNavProvider } from '../../modules/StepNav';
import { StatusListProvider } from '../../modules/Status';
import Localizer from '../../modules/Localizer';

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { useLocation, NavLink, Link } from 'react-router-dom';
import getSanitizedConfig from '../../../config/getSanitizedConfig';
import getSanitizedClientConfig from '../../../config/getSanitizedClientConfig';
import Arrow from '../../graphics/Arrow';
import Icon from '../../graphics/Icon';
@@ -13,7 +13,7 @@ const {
routes: {
admin,
},
} = getSanitizedConfig();
} = getSanitizedClientConfig();
const Sidebar = () => {
const location = useLocation();

View File

@@ -2,9 +2,9 @@ import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import Filter from '../Filter';
import Table from '../../layout/Table';
import getSanitizedConfig from '../../../config/getSanitizedConfig';
import getSanitizedClientConfig from '../../../config/getSanitizedClientConfig';
const { routes: { admin } } = getSanitizedConfig();
const { routes: { admin } } = getSanitizedClientConfig();
class SearchableTable extends Component {
constructor(props) {

View File

@@ -2,115 +2,127 @@ import React, { Component } from 'react';
import { createPortal } from 'react-dom';
import Button from '../../controls/Button';
import api from '../../../api';
import getSanitizedConfig from '../../../config/getSanitizedConfig';
import getSanitizedClientConfig from '../../../config/getSanitizedClientConfig';
import './index.scss';
const config = getSanitizedConfig();
const config = getSanitizedClientConfig();
class UploadMedia extends Component {
constructor() {
super();
this.state = {
dragging: false,
selectingFile: false,
files: null
}
files: null,
};
this.dropRef = React.createRef();
this.dragCounter = 0;
}
componentDidMount() {
let div = this.dropRef.current
div.addEventListener('dragenter', this.handleDragIn)
div.addEventListener('dragleave', this.handleDragOut)
div.addEventListener('dragover', this.handleDrag)
div.addEventListener('drop', this.handleDrop)
const div = this.dropRef.current;
div.addEventListener('dragenter', this.handleDragIn);
div.addEventListener('dragleave', this.handleDragOut);
div.addEventListener('dragover', this.handleDrag);
div.addEventListener('drop', this.handleDrop);
}
componentWillUnmount() {
let div = this.dropRef.current
div.removeEventListener('dragenter', this.handleDragIn)
div.removeEventListener('dragleave', this.handleDragOut)
div.removeEventListener('dragover', this.handleDrag)
div.removeEventListener('drop', this.handleDrop)
const div = this.dropRef.current;
div.removeEventListener('dragenter', this.handleDragIn);
div.removeEventListener('dragleave', this.handleDragOut);
div.removeEventListener('dragover', this.handleDrag);
div.removeEventListener('drop', this.handleDrop);
}
handleDrag = e => {
handleDrag = (e) => {
e.preventDefault();
e.stopPropagation();
}
handleDragIn = e => {
handleDragIn = (e) => {
e.preventDefault();
e.stopPropagation();
this.dragCounter++;
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
this.setState({ dragging: true })
this.setState({ dragging: true });
}
}
handleDragOut = e => {
handleDragOut = (e) => {
e.preventDefault();
e.stopPropagation();
this.dragCounter--;
if (this.dragCounter > 0) return;
this.setState({ dragging: false })
this.setState({ dragging: false });
}
handleDrop = e => {
handleDrop = (e) => {
e.preventDefault();
e.stopPropagation();
this.setState({ dragging: false })
this.setState({ dragging: false });
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
this.setState({
files: e.dataTransfer.files,
dragging: false
})
dragging: false,
});
e.dataTransfer.clearData();
this.dragCounter = 0;
} else {
this.setState({ dragging: false })
this.setState({ dragging: false });
}
}
handleSelectFile = e => {
handleSelectFile = (e) => {
e.preventDefault();
e.stopPropagation();
this.setState({
selectingFile: true
})
selectingFile: true,
});
}
setSelectingFile = selectingFile => {
this.setState({ selectingFile })
setSelectingFile = (selectingFile) => {
this.setState({ selectingFile });
}
render() {
return (
<div ref={this.dropRef} className={`upload-media${this.state.dragging ? ' dragging' : ''}`}>
<div
ref={this.dropRef}
className={`upload-media${this.state.dragging ? ' dragging' : ''}`}
>
<span className="instructions">Drag and drop a file here</span>
<span className="or">&mdash;or&mdash;</span>
<Button className="select-file" type="secondary" onClick={this.handleSelectFile}>Select a File</Button>
<UploadMediaForm files={this.state.files} selectingFile={this.state.selectingFile} config={config} setSelectingFile={this.setSelectingFile} />
<Button
className="select-file"
type="secondary"
onClick={this.handleSelectFile}
>
Select a File
</Button>
<UploadMediaForm
files={this.state.files}
selectingFile={this.state.selectingFile}
config={config}
setSelectingFile={this.setSelectingFile}
/>
</div>
)
);
}
}
// Need to set up a separate component with a Portal
// to make sure forms are not embedded within other forms
class UploadMediaForm extends Component {
constructor() {
super();
this.state = {
submitted: false
}
submitted: false,
};
this.formRef = React.createRef();
this.inputRef = React.createRef();
}
@@ -148,18 +160,23 @@ class UploadMediaForm extends Component {
const data = new FormData(this.formRef.current);
api.requests.post(`${this.props.config.serverURL}/upload`, data).then(
res => console.log(res),
err => {
(err) => {
console.warn(err);
}
},
);
}
render() {
return createPortal(
<form ref={this.formRef}>
<input type="file" name="files" ref={this.inputRef} />
<input
type="file"
name="files"
ref={this.inputRef}
/>
</form>,
document.getElementById('portal'))
document.getElementById('portal'),
);
}
}

View File

@@ -7,7 +7,7 @@ import ContentBlock from '../../layout/ContentBlock';
import Form from '../../forms/Form';
import RenderFields from '../../forms/RenderFields';
import FormSubmit from '../../forms/Submit';
import getSanitizedConfig from '../../../config/getSanitizedConfig';
import getSanitizedClientConfig from '../../../config/getSanitizedClientConfig';
import { useUser } from '../../data/User';
import './index.scss';
@@ -16,7 +16,7 @@ const {
routes: {
admin,
},
} = getSanitizedConfig();
} = getSanitizedClientConfig();
const passwordField = {
name: 'password',

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { Link } from 'react-router-dom';
import DefaultTemplate from '../../layout/DefaultTemplate';
import getSanitizedConfig from '../../../config/getSanitizedConfig';
import getSanitizedClientConfig from '../../../config/getSanitizedClientConfig';
import './index.scss';
@@ -9,7 +9,7 @@ const {
routes: {
admin,
},
} = getSanitizedConfig();
} = getSanitizedClientConfig();
const baseClass = 'dashboard';

View File

@@ -6,7 +6,7 @@ import Form from '../../forms/Form';
import Email from '../../forms/field-types/Email';
import Password from '../../forms/field-types/Password';
import FormSubmit from '../../forms/Submit';
import getSanitizedConfig from '../../../config/getSanitizedConfig';
import getSanitizedClientConfig from '../../../config/getSanitizedClientConfig';
import Button from '../../controls/Button';
import { useUser } from '../../data/User';
@@ -18,7 +18,7 @@ const {
routes: {
admin,
},
} = getSanitizedConfig();
} = getSanitizedClientConfig();
const Login = () => {
const { addStatus } = useStatusList();

View File

@@ -1,7 +1,7 @@
import React, { useEffect } from 'react';
import { useUser } from '../../data/User';
import ContentBlock from '../../layout/ContentBlock';
import getSanitizedConfig from '../../../config/getSanitizedConfig';
import getSanitizedClientConfig from '../../../config/getSanitizedClientConfig';
import Button from '../../controls/Button';
import './index.scss';
@@ -12,7 +12,7 @@ const {
routes: {
admin,
},
} = getSanitizedConfig();
} = getSanitizedClientConfig();
const Logout = () => {
const { logOut } = useUser();

View File

@@ -1,9 +1,9 @@
import React from 'react';
import getSanitizedConfig from '../../../config/getSanitizedConfig';
import getSanitizedClientConfig from '../../../config/getSanitizedClientConfig';
import Button from '../../controls/Button';
import DefaultTemplate from '../../layout/DefaultTemplate';
const { routes: { admin } } = getSanitizedConfig();
const { routes: { admin } } = getSanitizedClientConfig();
const NotFound = () => {
return (

View File

@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useRouteMatch, useHistory } from 'react-router-dom';
import getSanitizedConfig from '../../../../config/getSanitizedConfig';
import getSanitizedClientConfig from '../../../../config/getSanitizedClientConfig';
import DefaultTemplate from '../../../layout/DefaultTemplate';
import usePayloadAPI from '../../../../hooks/usePayloadAPI';
import Form from '../../../forms/Form';
@@ -16,9 +16,9 @@ import './index.scss';
const {
serverURL,
routes: {
admin
}
} = getSanitizedConfig();
admin,
},
} = getSanitizedClientConfig();
const baseClass = 'collection-edit';
@@ -33,14 +33,14 @@ const EditView = (props) => {
status: {
message: json.message,
type: 'success',
}
},
});
});
} : null;
const [{ data }] = usePayloadAPI(
(isEditing ? `${serverURL}/${collection.slug}/${id}` : null),
{ initialParams: { 'fallback-locale': 'null' } }
{ initialParams: { 'fallback-locale': 'null' } },
);
const nav = [{
@@ -50,12 +50,12 @@ const EditView = (props) => {
if (isEditing) {
nav.push({
label: data ? data[collection.useAsTitle] : ''
})
label: data ? data[collection.useAsTitle] : '',
});
} else {
nav.push({
label: 'Create New'
})
label: 'Create New',
});
}
return (
@@ -66,26 +66,45 @@ const EditView = (props) => {
<header className={`${baseClass}__header`}>
{isEditing && (
<h1>
Edit {Object.keys(data).length > 0 &&
(data[collection.useAsTitle] ? data[collection.useAsTitle] : '[Untitled]')
Edit
{' '}
{Object.keys(data).length > 0
&& (data[collection.useAsTitle] ? data[collection.useAsTitle] : '[Untitled]')
}
</h1>
)}
{!isEditing &&
<h1>Create New {collection.labels.singular}</h1>
{!isEditing
&& (
<h1>
Create New
{' '}
{collection.labels.singular}
</h1>
)
}
</header>
<Form className={`${baseClass}__form`} method={id ? 'put' : 'post'} action={`${serverURL}/${collection.slug}${id ? `/${id}` : ''}`} handleAjaxResponse={handleAjaxResponse}>
<StickyHeader showStatus={true}
<Form
className={`${baseClass}__form`}
method={id ? 'put' : 'post'}
action={`${serverURL}/${collection.slug}${id ? `/${id}` : ''}`}
handleAjaxResponse={handleAjaxResponse}
>
<StickyHeader
showStatus
content={
<APIURL url={isEditing && `${serverURL}/${collection.slug}/${data.id}`} />
} action={
}
action={(
<>
<Button type="secondary">Preview</Button>
<FormSubmit>Save</FormSubmit>
</>
} />
<RenderFields fields={collection.fields} initialData={data} />
)}
/>
<RenderFields
fields={collection.fields}
initialData={data}
/>
</Form>
</DefaultTemplate>
);

View File

@@ -3,7 +3,7 @@ import { useLocation } from 'react-router-dom';
import queryString from 'qs';
import PropTypes from 'prop-types';
import usePayloadAPI from '../../../../hooks/usePayloadAPI';
import getSanitizedConfig from '../../../../config/getSanitizedConfig';
import getSanitizedClientConfig from '../../../../config/getSanitizedClientConfig';
import DefaultTemplate from '../../../layout/DefaultTemplate';
import HeadingButton from '../../../modules/HeadingButton';
import SearchableTable from '../../../modules/SearchableTable';
@@ -16,7 +16,7 @@ const {
routes: {
admin,
},
} = getSanitizedConfig();
} = getSanitizedClientConfig();
const ListView = (props) => {
const { collection } = props;

View File

@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import getSanitizedConfig from '../../../../config/getSanitizedConfig';
import getSanitizedClientConfig from '../../../../config/getSanitizedClientConfig';
import DefaultTemplate from '../../../layout/DefaultTemplate';
import usePayloadAPI from '../../../../hooks/usePayloadAPI';
import Form from '../../../forms/Form';
@@ -15,9 +15,9 @@ import './index.scss';
const {
serverURL,
routes: {
admin
}
} = getSanitizedConfig();
admin,
},
} = getSanitizedClientConfig();
const baseClass = 'global-edit';
@@ -26,7 +26,7 @@ const EditView = (props) => {
const [{ data }] = usePayloadAPI(
`${serverURL}/globals/${global.slug}`,
{ initialParams: { 'fallback-locale': 'null' } }
{ initialParams: { 'fallback-locale': 'null' } },
);
const nav = [{
@@ -41,20 +41,32 @@ const EditView = (props) => {
>
<header className={`${baseClass}__header`}>
<h1>
Edit {global.label}
Edit
{' '}
{global.label}
</h1>
</header>
<Form className={`${baseClass}__form`} method={data ? 'put' : 'post'} action={`${serverURL}/globals/${global.slug}`}>
<StickyHeader showStatus={true}
<Form
className={`${baseClass}__form`}
method={data ? 'put' : 'post'}
action={`${serverURL}/globals/${global.slug}`}
>
<StickyHeader
showStatus
content={
<APIURL url={`${serverURL}/globals/${global.slug}`} />
} action={
}
action={(
<>
<Button type="secondary">Preview</Button>
<FormSubmit>Save</FormSubmit>
</>
} />
<RenderFields fields={global.fields} initialData={data} />
)}
/>
<RenderFields
fields={global.fields}
initialData={data}
/>
</Form>
</DefaultTemplate>
);

View File

@@ -0,0 +1,5 @@
import config from 'payload-config';
import sanitizeConfig from '../../utilities/sanitizeConfig';
const getSanitizedClientConfig = () => sanitizeConfig(config);
export default getSanitizedClientConfig;

View File

@@ -104,8 +104,8 @@ module.exports = (config) => {
modules: ['node_modules', path.resolve(__dirname, '../../../node_modules')],
alias: {
'payload-scss-overrides': config.paths.scssOverrides,
'payload-custom-components': config.paths.components,
'payload-config': config.paths.config,
'payload/Sidebar': (config.customComponents.layout && config.customComponents.layout.Sidebar) ? config.customComponents.layout.Sidebar : path.resolve(__dirname, '../components/layout/Sidebar/index.js'),
},
},
};

View File

@@ -11,13 +11,13 @@ const baseUserFields = require('./auth/baseFields');
const baseUploadFields = require('./uploads/baseUploadFields');
const baseImageFields = require('./uploads/baseImageFields');
const registerUploadRoutes = require('./uploads/routes');
const registerConfigRoute = require('./routes/config');
const validateCollection = require('./collections/validate');
const buildCollectionSchema = require('./collections/buildSchema');
const registerCollectionRoutes = require('./collections/registerRoutes');
const validateGlobals = require('./globals/validate');
const registerGlobalSchema = require('./globals/registerSchema');
const registerGlobalRoutes = require('./globals/registerRoutes');
const sanitizeConfig = require('./utilities/sanitizeConfig');
class Payload {
constructor(options) {
@@ -28,19 +28,18 @@ class Payload {
this.getCollections.bind(this);
this.getGlobals.bind(this);
// Setup & initialization
connectMongoose(options.config.mongoURL);
registerExpressMiddleware(options);
initPassport(options.app);
initUploads(options);
initCORS(options);
registerConfigRoute(options, this.getCollections, this.getGlobals);
// Bind options, app, router
this.config = options.config;
this.app = options.app;
this.config = sanitizeConfig(options.config);
this.router = options.router;
// Setup & initialization
connectMongoose(this.config.mongoURL);
registerExpressMiddleware(this.app, this.config, this.router);
initPassport(this.app);
initUploads(this.app, this.config);
initCORS(this.app, this.config);
// Register and bind required collections
this.registerUser();
this.registerUpload();
@@ -61,7 +60,7 @@ class Payload {
this.registerGlobals(this.config.globals);
// Enable client webpack
if (!this.config.disableAdmin) initWebpack(options);
if (!this.config.disableAdmin) initWebpack(this.app, this.config);
}
registerUser() {

View File

@@ -1,4 +1,4 @@
const initCORS = ({ config, app }) => {
const initCORS = (app, config) => {
if (config.cors) {
app.use((req, res, next) => {
if (config.cors.indexOf(req.headers.origin) > -1) {

View File

@@ -4,7 +4,7 @@ const bodyParser = require('body-parser');
const methodOverride = require('method-override');
const localizationMiddleware = require('../localization/middleware');
const registerExpressMiddleware = ({ app, config, router }) => {
const registerExpressMiddleware = (app, config, router) => {
app.use(express.json());
app.use(methodOverride('X-HTTP-Method-Override'));
app.use(express.urlencoded({ extended: true }));

View File

@@ -1,6 +1,6 @@
const express = require('express');
const initUploads = ({ config, app }) => {
const initUploads = (app, config) => {
const staticUrl = config.staticUrl ? config.staticUrl : `/${config.staticDir}`;
app.use(staticUrl, express.static(config.staticDir));
};

View File

@@ -1,10 +1,9 @@
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const path = require('path');
const getWebpackDevConfig = require('../client/config/getWebpackDevConfig');
const initWebpack = ({ config, app }) => {
const initWebpack = (app, config) => {
const webpackDevConfig = getWebpackDevConfig(config);
const compiler = webpack(webpackDevConfig);

View File

@@ -1,35 +0,0 @@
const passport = require('passport');
const registerConfigRoute = ({ router, config }, getCollections, getGlobals) => {
router.use('/config',
passport.authenticate('jwt', { session: false }),
(req, res) => {
const registeredCollections = getCollections();
const globals = getGlobals();
const collections = {};
const contentBlocks = {};
Object.keys(registeredCollections).forEach((key) => {
const fullConfig = registeredCollections[key].config;
const collectionConfig = { ...fullConfig };
delete collectionConfig.plugins;
delete collectionConfig.policies;
if (collectionConfig.useAsContentBlock) {
contentBlocks[collectionConfig.slug] = collectionConfig;
}
collections[collectionConfig.slug] = collectionConfig;
});
res.json({
...config,
collections,
globals,
contentBlocks,
});
});
};
module.exports = registerConfigRoute;

View File

@@ -1,14 +1,13 @@
import config from 'payload-config';
const getSanitizedConfig = () => {
const sanitizeConfig = (config) => {
const sanitizedConfig = { ...config };
sanitizedConfig.routes = {
admin: (config.routes && config.routes.admin) ? config.routes.admin : '/admin',
api: (config.routes && config.routes.api) ? config.routes.api : '/api',
};
sanitizedConfig.customComponents = { ...(config.customComponents || {}) };
return sanitizedConfig;
};
export default getSanitizedConfig;
module.exports = sanitizeConfig;

View File

@@ -1,7 +1,7 @@
import usePayloadAPI from './src/client/hooks/usePayloadAPI';
import getSanitizedConfig from './src/client/config/getSanitizedConfig';
import getSanitizedClientConfig from './src/client/config/getSanitizedClientConfig';
export default {
usePayloadAPI,
getSanitizedConfig,
getSanitizedClientConfig,
};