From c96fa613bce77ab5fa7bbacc3bab257ecc9dc841 Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Mon, 11 Nov 2024 13:59:05 -0500 Subject: [PATCH] feat!: on demand rsc (#8364) Currently, Payload renders all custom components on initial compile of the admin panel. This is problematic for two key reasons: 1. Custom components do not receive contextual data, i.e. fields do not receive their field data, edit views do not receive their document data, etc. 2. Components are unnecessarily rendered before they are used This was initially required to support React Server Components within the Payload Admin Panel for two key reasons: 1. Fields can be dynamically rendered within arrays, blocks, etc. 2. Documents can be recursively rendered within a "drawer" UI, i.e. relationship fields 3. Payload supports server/client component composition In order to achieve this, components need to be rendered on the server and passed as "slots" to the client. Currently, the pattern for this is to render custom server components in the "client config". Then when a view or field is needed to be rendered, we first check the client config for a "pre-rendered" component, otherwise render our client-side fallback component. But for the reasons listed above, this pattern doesn't exactly make custom server components very useful within the Payload Admin Panel, which is where this PR comes in. Now, instead of pre-rendering all components on initial compile, we're able to render custom components _on demand_, only as they are needed. To achieve this, we've established [this pattern](https://github.com/payloadcms/payload/pull/8481) of React Server Functions in the Payload Admin Panel. With Server Functions, we can iterate the Payload Config and return JSX through React's `text/x-component` content-type. This means we're able to pass contextual props to custom components, such as data for fields and views. ## Breaking Changes 1. Add the following to your root layout file, typically located at `(app)/(payload)/layout.tsx`: ```diff /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ + import type { ServerFunctionClient } from 'payload' import config from '@payload-config' import { RootLayout } from '@payloadcms/next/layouts' import { handleServerFunctions } from '@payloadcms/next/utilities' import React from 'react' import { importMap } from './admin/importMap.js' import './custom.scss' type Args = { children: React.ReactNode } + const serverFunctions: ServerFunctionClient = async function (args) { + 'use server' + return handleServerFunctions({ + ...args, + config, + importMap, + }) + } const Layout = ({ children }: Args) => ( {children} ) export default Layout ``` 2. If you were previously posting to the `/api/form-state` endpoint, it no longer exists. Instead, you'll need to invoke the `form-state` Server Function, which can be done through the _new_ `getFormState` utility: ```diff - import { getFormState } from '@payloadcms/ui' - const { state } = await getFormState({ - apiRoute: '', - body: { - // ... - }, - serverURL: '' - }) + const { getFormState } = useServerFunctions() + + const { state } = await getFormState({ + // ... + }) ``` ## Breaking Changes ```diff - useFieldProps() - useCellProps() ``` More details coming soon. --------- Co-authored-by: Alessio Gravili Co-authored-by: Jarrod Flesch Co-authored-by: James --- app/(payload)/layout.tsx | 17 +- docs/admin/fields.mdx | 1 - docs/lexical/converters.mdx | 18 +- .../src/app/(payload)/layout.tsx | 4 +- .../hierarchy/src/app/(payload)/layout.tsx | 2 +- .../multi-tenant/src/app/(payload)/layout.tsx | 4 +- next.config.mjs | 5 + package.json | 7 +- .../db-mongodb/src/countGlobalVersions.ts | 48 + packages/db-mongodb/src/countVersions.ts | 48 + packages/db-mongodb/src/index.ts | 6 +- .../src/utilities/buildJoinAggregation.ts | 12 +- .../db-mongodb/src/utilities/handleError.ts | 2 +- packages/db-postgres/src/index.ts | 4 + packages/db-sqlite/src/index.ts | 4 + packages/db-vercel-postgres/src/index.ts | 4 + packages/drizzle/src/countGlobalVersions.ts | 42 + packages/drizzle/src/countVersions.ts | 40 + packages/drizzle/src/index.ts | 2 + packages/drizzle/src/upsertRow/index.ts | 2 +- .../DocumentHeader/Tabs/Tab/index.tsx | 31 +- .../elements/DocumentHeader/Tabs/index.tsx | 35 +- .../Tabs/tabs/VersionsPill/index.tsx | 9 +- .../src/elements/DocumentHeader/index.tsx | 29 +- .../src/elements/EmailAndUsername/index.tsx | 114 - packages/next/src/elements/Logo/index.tsx | 35 +- .../next/src/elements/Nav/index.client.tsx | 63 +- packages/next/src/elements/Nav/index.tsx | 102 +- packages/next/src/exports/layouts.ts | 1 + packages/next/src/exports/utilities.ts | 1 + packages/next/src/exports/views.ts | 2 - .../fetchAPI-multipart/processMultipart.ts | 23 +- .../next/src/layouts/Root/NestProviders.tsx | 30 + packages/next/src/layouts/Root/index.tsx | 40 +- .../next/src/routes/rest/buildFormState.ts | 50 - packages/next/src/routes/rest/index.ts | 8 - packages/next/src/routes/rest/og/image.tsx | 24 +- packages/next/src/routes/rest/og/index.tsx | 18 +- packages/next/src/routes/rest/routeError.ts | 63 +- packages/next/src/templates/Default/index.tsx | 142 +- .../next/src/utilities/getClientConfig.ts | 18 + .../src/utilities/handleServerFunctions.ts | 37 + packages/next/src/utilities/initPage/index.ts | 7 +- packages/next/src/utilities/initReq.ts | 11 +- .../src/views/API/LocaleSelector/index.tsx | 1 - packages/next/src/views/API/index.client.tsx | 27 +- .../next/src/views/Account/Settings/index.tsx | 2 +- packages/next/src/views/Account/index.tsx | 112 +- .../views/CreateFirstUser/index.client.tsx | 48 +- .../next/src/views/CreateFirstUser/index.tsx | 39 +- .../src/views/Dashboard/Default/index.tsx | 100 +- packages/next/src/views/Dashboard/index.tsx | 54 +- .../src/views/Document/getDocPreferences.ts | 60 + .../src/views/Document/getDocumentData.ts | 52 + .../src/views/Document/getDocumentData.tsx | 62 - .../next/src/views/Document/getIsLocked.ts | 86 + .../next/src/views/Document/getVersions.ts | 240 + .../src/views/Document/getViewsFromConfig.tsx | 40 +- .../views/Document/handleServerFunction.tsx | 195 + packages/next/src/views/Document/index.tsx | 365 +- .../views/Document/renderDocumentSlots.tsx | 136 + packages/next/src/views/Edit/index.client.tsx | 33 - packages/next/src/views/Edit/index.tsx | 11 +- .../ForgotPasswordForm/index.tsx | 4 +- .../next/src/views/List/Default/index.tsx | 259 - .../src/views/List/handleServerFunction.tsx | 194 + packages/next/src/views/List/index.tsx | 234 +- .../src/views/List/renderListViewSlots.tsx | 66 + .../src/views/LivePreview/index.client.tsx | 111 +- .../next/src/views/Login/LoginField/index.tsx | 4 +- packages/next/src/views/Login/index.tsx | 47 +- .../ResetPassword/ResetPasswordForm/index.tsx | 15 +- .../next/src/views/Root/getViewFromConfig.ts | 176 +- packages/next/src/views/Root/index.tsx | 70 +- .../next/src/views/Version/Default/index.tsx | 14 +- .../next/src/views/Versions/buildColumns.tsx | 111 +- .../Versions/cells/AutosaveCell/index.tsx | 12 +- .../views/Versions/cells/CreatedAt/index.tsx | 18 +- .../src/views/Versions/cells/ID/index.tsx | 6 +- .../next/src/views/Versions/index.client.tsx | 22 +- packages/next/src/views/Versions/index.tsx | 5 +- packages/next/tsconfig.json | 2 +- packages/payload/src/admin/RichText.ts | 23 +- packages/payload/src/admin/elements/Cell.ts | 17 +- packages/payload/src/admin/fields/Array.ts | 7 +- packages/payload/src/admin/fields/Blocks.ts | 5 +- packages/payload/src/admin/fields/Checkbox.ts | 1 + packages/payload/src/admin/fields/Code.ts | 3 +- .../payload/src/admin/fields/Collapsible.ts | 13 +- packages/payload/src/admin/fields/Date.ts | 1 + packages/payload/src/admin/fields/Email.ts | 2 +- packages/payload/src/admin/fields/Group.ts | 12 +- packages/payload/src/admin/fields/Hidden.ts | 13 +- packages/payload/src/admin/fields/JSON.ts | 1 + packages/payload/src/admin/fields/Join.ts | 4 +- packages/payload/src/admin/fields/Number.ts | 1 + packages/payload/src/admin/fields/Point.ts | 1 + packages/payload/src/admin/fields/Radio.ts | 1 + .../payload/src/admin/fields/Relationship.ts | 1 + packages/payload/src/admin/fields/RichText.ts | 9 +- packages/payload/src/admin/fields/Row.ts | 3 +- packages/payload/src/admin/fields/Select.ts | 1 + packages/payload/src/admin/fields/Tabs.ts | 12 +- packages/payload/src/admin/fields/Text.ts | 1 + packages/payload/src/admin/fields/Textarea.ts | 1 + packages/payload/src/admin/fields/UI.ts | 9 + packages/payload/src/admin/fields/Upload.ts | 1 + .../payload/src/admin/forms/Description.ts | 3 +- packages/payload/src/admin/forms/Error.ts | 2 - packages/payload/src/admin/forms/Field.ts | 94 +- packages/payload/src/admin/forms/Form.ts | 64 +- packages/payload/src/admin/forms/Label.ts | 3 +- packages/payload/src/admin/functions/index.ts | 59 + packages/payload/src/admin/types.ts | 180 +- packages/payload/src/admin/views/types.ts | 17 +- .../payload/src/auth/ensureUsernameOrEmail.ts | 4 +- .../payload/src/auth/operations/local/auth.ts | 5 +- packages/payload/src/auth/operations/login.ts | 10 +- .../local/generatePasswordSaltHash.ts | 2 +- .../src/auth/strategies/local/register.ts | 4 +- .../parsePayloadComponent.ts | 10 +- .../payload/src/collections/config/client.ts | 208 +- .../src/collections/config/sanitize.ts | 7 +- .../payload/src/collections/config/types.ts | 5 +- .../collections/operations/countVersions.ts | 112 + .../src/collections/operations/local/count.ts | 1 + .../operations/local/countVersions.ts | 45 + .../src/collections/operations/local/index.ts | 2 + .../src/collections/operations/utils.ts | 7 + packages/payload/src/config/client.ts | 77 +- packages/payload/src/config/types.ts | 89 +- .../payload/src/database/sanitizeJoinQuery.ts | 12 +- packages/payload/src/database/types.ts | 13 + .../payload/src/errors/ValidationError.ts | 5 +- packages/payload/src/exports/shared.ts | 7 +- packages/payload/src/fields/config/client.ts | 317 +- .../payload/src/fields/config/sanitize.ts | 33 +- .../src/fields/config/sanitizeJoinField.ts | 6 +- packages/payload/src/fields/config/types.ts | 226 +- packages/payload/src/fields/getFieldPaths.ts | 69 +- .../src/fields/hooks/afterChange/promise.ts | 12 +- .../hooks/afterChange/traverseFields.ts | 3 +- .../src/fields/hooks/afterRead/promise.ts | 12 +- .../fields/hooks/afterRead/traverseFields.ts | 3 +- .../src/fields/hooks/beforeChange/index.ts | 3 +- .../src/fields/hooks/beforeChange/promise.ts | 41 +- .../hooks/beforeChange/traverseFields.ts | 6 +- .../fields/hooks/beforeDuplicate/promise.ts | 12 +- .../hooks/beforeDuplicate/traverseFields.ts | 3 +- .../fields/hooks/beforeValidate/promise.ts | 12 +- .../hooks/beforeValidate/traverseFields.ts | 3 +- packages/payload/src/globals/config/client.ts | 129 +- .../payload/src/globals/config/sanitize.ts | 4 +- .../globals/operations/countGlobalVersions.ts | 79 + .../operations/local/countGlobalVersions.ts | 45 + .../src/globals/operations/local/index.ts | 2 + packages/payload/src/index.ts | 79 +- .../src/utilities/configToJSONSchema.ts | 4 +- .../payload/src/utilities/createLocalReq.ts | 23 +- .../payload/src/utilities/deepCopyObject.ts | 35 + .../payload/src/utilities/formatErrors.ts | 66 + .../src/utilities/mergeListSearchAndWhere.ts | 72 - packages/payload/src/versions/saveVersion.ts | 11 +- packages/payload/src/versions/types.ts | 2 +- .../Forms/DynamicPriceSelector.tsx | 6 +- .../MetaDescriptionComponent.tsx | 48 +- .../fields/MetaImage/MetaImageComponent.tsx | 49 +- .../fields/MetaTitle/MetaTitleComponent.tsx | 49 +- .../src/fields/Preview/PreviewComponent.tsx | 8 +- packages/plugin-seo/src/types.ts | 4 +- packages/plugin-stripe/src/ui/LinkToDoc.tsx | 10 +- packages/richtext-lexical/package.json | 21 +- packages/richtext-lexical/src/cell/index.tsx | 146 - .../richtext-lexical/src/cell/rscEntry.tsx | 117 + .../src/exports/client/index.ts | 1 - .../src/exports/server/rsc.ts | 2 + .../blocks/client/component/BlockContent.tsx | 28 +- .../blocks/client/component/index.tsx | 152 +- .../blocks/client/componentInline/index.tsx | 35 +- .../src/features/blocks/client/index.tsx | 70 +- .../features/blocks/client/plugin/index.tsx | 35 +- .../src/features/blocks/server/index.ts | 35 +- .../src/features/blocks/server/validate.ts | 13 +- .../client/plugins/TablePlugin/index.tsx | 10 +- .../experimental_table/server/index.ts | 8 +- .../floatingLinkEditor/LinkEditor/index.tsx | 10 +- .../src/features/link/server/index.ts | 8 +- .../src/features/link/server/validate.ts | 13 +- .../components/RelationshipComponent.tsx | 12 +- .../toolbars/fixed/client/Toolbar/index.tsx | 8 +- .../toolbars/shared/ToolbarDropdown/index.tsx | 6 +- .../src/features/toolbars/types.ts | 4 +- .../src/features/typesServer.ts | 15 +- .../upload/client/component/index.tsx | 13 +- .../features/upload/server/feature.server.ts | 7 +- .../src/features/upload/server/validate.ts | 11 +- packages/richtext-lexical/src/field/Field.tsx | 59 +- packages/richtext-lexical/src/field/index.tsx | 22 +- .../richtext-lexical/src/field/rscEntry.tsx | 46 + packages/richtext-lexical/src/index.ts | 18 +- .../src/lexical/LexicalProvider.tsx | 7 +- .../config/client/EditorConfigProvider.tsx | 18 +- .../LexicalMenu.tsx | 32 +- .../LexicalTypeaheadMenuPlugin/index.tsx | 7 +- .../LexicalTypeaheadMenuPlugin/types.ts | 6 +- .../src/lexical/plugins/SlashMenu/index.tsx | 41 +- .../src/lexical/theme/EditorTheme.tsx | 1 - packages/richtext-lexical/src/types.ts | 32 +- .../src/utilities/fieldsDrawer/Drawer.tsx | 49 +- .../utilities/fieldsDrawer/DrawerContent.tsx | 132 +- .../fieldsDrawer/useLexicalDrawer.tsx | 2 +- .../src/utilities/generateComponentMap.tsx | 137 - .../src/utilities/generateImportMap.tsx | 5 +- .../src/utilities/generateSchemaMap.ts | 23 +- .../src/utilities/initLexicalFeatures.ts | 150 + .../upgradeDocumentFieldsRecursively.ts | 7 +- .../src/utilities/useLexicalFeature.tsx | 15 +- .../richtext-lexical/src/validate/index.ts | 3 +- packages/richtext-slate/package.json | 14 +- packages/richtext-slate/src/cell/index.tsx | 48 - packages/richtext-slate/src/cell/rscEntry.tsx | 87 + .../src/exports/client/index.ts | 5 +- .../richtext-slate/src/exports/server/rsc.ts | 2 + .../richtext-slate/src/field/RichText.tsx | 67 +- .../src/field/createFeatureMap.ts | 7 +- .../src/field/elements/link/Button/index.tsx | 42 +- .../src/field/elements/link/Element/index.tsx | 48 +- .../field/elements/link/LinkDrawer/index.tsx | 65 +- .../field/elements/link/LinkDrawer/types.ts | 1 + .../elements/relationship/Element/index.tsx | 11 +- .../upload/Element/UploadDrawer/index.tsx | 113 +- .../field/elements/upload/Element/index.tsx | 13 +- packages/richtext-slate/src/field/index.tsx | 34 +- .../richtext-slate/src/field/rscEntry.tsx | 203 + packages/richtext-slate/src/field/types.ts | 12 +- .../src/generateComponentMap.tsx | 142 - .../richtext-slate/src/generateSchemaMap.ts | 40 +- packages/richtext-slate/src/index.tsx | 16 +- packages/richtext-slate/src/types.ts | 14 +- .../src/utilities/SlatePropsProvider.tsx | 27 + .../src/utilities/useSlatePlugin.tsx | 6 +- packages/ui/package.json | 41 +- .../ui/src/elements/AddNewRelation/index.tsx | 4 +- packages/ui/src/elements/AppHeader/index.tsx | 43 +- packages/ui/src/elements/Autosave/index.tsx | 327 +- .../elements/BulkUpload/ActionsBar/index.tsx | 4 +- .../BulkUpload/AddingFilesView/index.tsx | 17 +- .../elements/BulkUpload/EditForm/index.tsx | 79 +- .../BulkUpload/FormsManager/index.tsx | 58 +- packages/ui/src/elements/BulkUpload/index.tsx | 22 +- .../ui/src/elements/ColumnSelector/index.tsx | 35 +- .../ui/src/elements/DeleteDocument/index.tsx | 4 +- .../src/elements/DocumentControls/index.tsx | 108 +- .../elements/DocumentDrawer/DrawerContent.tsx | 154 +- .../DocumentDrawer/DrawerHeader/index.scss | 73 + .../DocumentDrawer/DrawerHeader/index.tsx | 40 + .../src/elements/DocumentDrawer/Provider.tsx | 49 + .../ui/src/elements/DocumentDrawer/index.scss | 50 - .../ui/src/elements/DocumentDrawer/index.tsx | 13 +- .../ui/src/elements/DocumentDrawer/types.ts | 8 +- .../ui/src/elements/DocumentFields/index.tsx | 75 +- .../src/elements/DocumentLocked/index.scss | 0 .../src/elements/DocumentLocked/index.tsx | 6 +- .../src/elements/DocumentTakeOver/index.scss | 0 .../src/elements/DocumentTakeOver/index.tsx | 4 +- packages/ui/src/elements/Drawer/index.tsx | 80 +- .../src/elements/DuplicateDocument/index.tsx | 5 +- packages/ui/src/elements/EditMany/index.tsx | 182 +- .../src/elements/EmailAndUsername/index.tsx | 68 + .../ui/src/elements/FieldSelect/index.tsx | 75 +- .../elements/LeaveWithoutSaving/index.scss | 0 .../src/elements/LeaveWithoutSaving/index.tsx | 6 +- .../LeaveWithoutSaving/usePreventLeave.tsx | 0 .../ui/src/elements/ListControls/index.tsx | 46 +- .../src/elements/ListDrawer/DrawerContent.tsx | 452 +- .../ui/src/elements/ListDrawer/Provider.tsx | 47 + packages/ui/src/elements/ListDrawer/index.tsx | 2 - packages/ui/src/elements/ListDrawer/types.ts | 10 +- .../ui/src/elements/ListHeader/index.scss | 12 - packages/ui/src/elements/ListHeader/index.tsx | 19 - packages/ui/src/elements/Locked/index.tsx | 8 +- packages/ui/src/elements/Logout/index.tsx | 26 +- .../ui/src/elements/PreviewButton/index.tsx | 17 +- .../elements/PreviewButton/usePreviewURL.tsx | 4 +- .../ui/src/elements/PublishButton/index.tsx | 36 +- .../elements/RelationshipTable/Pagination.tsx | 26 + .../RelationshipTable/TableWrapper.tsx | 46 - .../cells/DrawerLink/index.tsx | 26 +- .../src/elements/RelationshipTable/index.tsx | 323 +- .../ui/src/elements/RenderComponent/index.tsx | 17 + .../elements/RenderCustomComponent/index.tsx | 19 + .../src/elements/RenderIfInViewport/index.tsx | 34 + .../elements/RenderServerComponent/index.tsx | 105 + packages/ui/src/elements/SaveButton/index.tsx | 16 +- .../ui/src/elements/SaveDraftButton/index.tsx | 32 +- packages/ui/src/elements/SelectRow/index.tsx | 12 +- packages/ui/src/elements/SortColumn/index.tsx | 13 +- packages/ui/src/elements/Status/index.tsx | 40 +- packages/ui/src/elements/StepNav/index.tsx | 27 +- .../Table/DefaultCell/fields/File/index.tsx | 13 +- .../DefaultCell/fields/Relationship/index.tsx | 12 +- .../src/elements/Table/DefaultCell/index.tsx | 71 +- packages/ui/src/elements/Table/RenderCell.tsx | 41 - .../Table/TableCellProvider/index.tsx | 47 - packages/ui/src/elements/Table/index.tsx | 45 +- .../TableColumns/RenderDefaultCell/index.scss | 15 + .../TableColumns/RenderDefaultCell/index.tsx | 47 + .../TableColumns/buildColumnState.tsx | 247 +- .../elements/TableColumns/filterFields.tsx | 12 +- .../TableColumns/getInitialColumns.ts | 16 +- .../ui/src/elements/TableColumns/index.tsx | 211 +- packages/ui/src/elements/Upload/index.tsx | 35 +- .../ui/src/elements/ViewDescription/index.tsx | 10 +- .../WhereBuilder/Condition/Date/index.tsx | 4 +- .../WhereBuilder/Condition/Date/types.ts | 14 +- .../Condition/DefaultFilter/index.tsx | 94 + .../WhereBuilder/Condition/Number/index.tsx | 1 + .../WhereBuilder/Condition/Number/types.ts | 12 +- .../Condition/Relationship/index.tsx | 20 +- .../Condition/Relationship/types.ts | 12 +- .../WhereBuilder/Condition/Select/types.ts | 15 +- .../WhereBuilder/Condition/Text/index.tsx | 1 + .../WhereBuilder/Condition/Text/types.ts | 12 +- .../elements/WhereBuilder/Condition/index.tsx | 60 +- .../elements/WhereBuilder/Condition/types.ts | 9 +- .../ui/src/elements/WhereBuilder/index.tsx | 8 +- .../WhereBuilder/reduceClientFields.tsx | 8 +- .../ui/src/elements/WhereBuilder/types.ts | 11 +- packages/ui/src/exports/client/index.ts | 53 +- packages/ui/src/exports/rsc/index.ts | 1 + packages/ui/src/exports/shared/index.ts | 9 +- packages/ui/src/fields/Array/ArrayRow.tsx | 26 +- packages/ui/src/fields/Array/index.tsx | 147 +- packages/ui/src/fields/Blocks/BlockRow.tsx | 32 +- packages/ui/src/fields/Blocks/index.tsx | 100 +- packages/ui/src/fields/Checkbox/Input.tsx | 44 +- packages/ui/src/fields/Checkbox/index.tsx | 63 +- packages/ui/src/fields/Code/index.tsx | 69 +- packages/ui/src/fields/Collapsible/index.tsx | 80 +- .../ui/src/fields/ConfirmPassword/index.tsx | 5 +- packages/ui/src/fields/DateTime/index.tsx | 77 +- packages/ui/src/fields/Email/index.tsx | 67 +- .../ui/src/fields/FieldDescription/index.tsx | 22 +- packages/ui/src/fields/FieldError/index.tsx | 21 +- packages/ui/src/fields/FieldLabel/index.tsx | 45 +- packages/ui/src/fields/Group/index.tsx | 71 +- packages/ui/src/fields/Hidden/index.tsx | 11 +- packages/ui/src/fields/JSON/index.tsx | 83 +- packages/ui/src/fields/Join/index.tsx | 41 +- packages/ui/src/fields/Number/index.tsx | 67 +- packages/ui/src/fields/Password/index.tsx | 33 +- packages/ui/src/fields/Password/input.tsx | 42 +- packages/ui/src/fields/Password/types.ts | 43 +- packages/ui/src/fields/Point/index.tsx | 99 +- packages/ui/src/fields/RadioGroup/index.tsx | 61 +- packages/ui/src/fields/Relationship/index.tsx | 57 +- packages/ui/src/fields/Row/index.tsx | 20 +- packages/ui/src/fields/Select/Input.tsx | 71 +- packages/ui/src/fields/Select/index.tsx | 44 +- packages/ui/src/fields/Tabs/Tab/index.tsx | 8 +- packages/ui/src/fields/Tabs/index.tsx | 219 +- packages/ui/src/fields/Text/Input.tsx | 45 +- packages/ui/src/fields/Text/index.tsx | 51 +- packages/ui/src/fields/Text/types.ts | 18 +- packages/ui/src/fields/Textarea/Input.tsx | 45 +- packages/ui/src/fields/Textarea/index.tsx | 56 +- packages/ui/src/fields/Textarea/types.ts | 26 +- packages/ui/src/fields/Upload/Input.tsx | 78 +- packages/ui/src/fields/Upload/index.tsx | 38 +- packages/ui/src/fields/index.tsx | 29 +- .../ui/src/forms/FieldPropsProvider/index.tsx | 77 - packages/ui/src/forms/Form/context.ts | 4 + packages/ui/src/forms/Form/fieldReducer.ts | 34 +- packages/ui/src/forms/Form/index.tsx | 172 +- .../ui/src/forms/Form/mergeServerFormState.ts | 33 +- .../forms/Form/reduceToSerializableFields.ts | 35 + packages/ui/src/forms/Form/types.ts | 40 +- packages/ui/src/forms/NullifyField/index.tsx | 5 + .../ui/src/forms/RenderFields/RenderField.tsx | 174 +- packages/ui/src/forms/RenderFields/index.tsx | 131 +- packages/ui/src/forms/RenderFields/types.ts | 22 +- packages/ui/src/forms/RowLabel/index.tsx | 48 +- packages/ui/src/forms/RowLabel/types.ts | 8 +- .../WatchChildErrors/buildPathSegments.ts | 11 +- .../ui/src/forms/WatchChildErrors/index.tsx | 2 +- .../src/forms/buildStateFromSchema/index.tsx | 73 - .../addFieldStatePromise.ts | 245 +- .../calculateDefaultValues/index.ts | 0 .../calculateDefaultValues/iterateFields.ts | 2 + .../calculateDefaultValues/promise.ts | 2 + .../fieldSchemasToFormState.spec.js} | 10 +- .../getFilterOptionsQuery.ts | 0 .../forms/fieldSchemasToFormState/index.tsx | 105 + .../iterateFields.ts | 102 +- .../fieldSchemasToFormState/renderField.tsx | 249 + .../forms/fieldSchemasToFormState/types.ts | 30 + packages/ui/src/forms/useField/index.tsx | 24 +- packages/ui/src/forms/useField/types.ts | 11 +- .../forms/withCondition/WatchCondition.tsx | 11 +- packages/ui/src/forms/withCondition/index.tsx | 25 +- packages/ui/src/graphics/Account/index.tsx | 14 +- packages/ui/src/hooks/useDebouncedEffect.ts | 2 +- packages/ui/src/hooks/useIgnoredEffect.ts | 32 + packages/ui/src/hooks/usePayloadAPI.ts | 2 +- packages/ui/src/hooks/useUseAsTitle.ts | 8 +- .../Actions/SetViewActions/index.tsx | 21 - packages/ui/src/providers/Actions/index.tsx | 39 +- .../src/providers/Config/RenderComponent.tsx | 88 - .../Config/createClientConfig/collections.tsx | 419 - .../Config/createClientConfig/fields.tsx | 603 - .../Config/createClientConfig/getComponent.ts | 63 - .../getCreateMappedComponent.tsx | 160 - .../Config/createClientConfig/globals.tsx | 230 - .../Config/createClientConfig/index.tsx | 210 - packages/ui/src/providers/Config/index.tsx | 30 +- .../ui/src/providers/DocumentInfo/index.tsx | 558 +- .../ui/src/providers/DocumentInfo/types.ts | 46 +- .../DocumentInfo/useGetDocPermissions.tsx | 115 + packages/ui/src/providers/EditDepth/index.tsx | 10 +- packages/ui/src/providers/ListInfo/index.tsx | 42 - packages/ui/src/providers/ListQuery/index.tsx | 163 +- packages/ui/src/providers/Root/index.tsx | 116 +- packages/ui/src/providers/Selection/index.tsx | 1 + .../src/providers/ServerFunctions/index.tsx | 222 + .../utilities/buildFieldSchemaMap/index.ts | 112 +- .../buildFieldSchemaMap/traverseFields.ts | 73 +- .../utilities/buildFieldSchemaMap/types.ts | 3 - packages/ui/src/utilities/buildFormState.ts | 521 +- packages/ui/src/utilities/buildTableState.ts | 259 + packages/ui/src/utilities/flattenFieldMap.ts | 18 +- packages/ui/src/utilities/getFormState.ts | 43 - packages/ui/src/utilities/groupNavItems.ts | 38 +- .../src/utilities/handleFormStateLocking.ts | 151 + .../src/utilities/mergeListSearchAndWhere.ts | 59 + packages/ui/src/utilities/renderTable.tsx | 110 + .../src/views/Edit}/Auth/APIKey.tsx | 29 +- .../src/views/Edit}/Auth/index.scss | 2 +- .../src/views/Edit}/Auth/index.tsx | 50 +- .../src/views/Edit}/Auth/types.ts | 2 +- .../views/Edit}/SetDocumentStepNav/index.tsx | 20 +- .../views/Edit}/SetDocumentTitle/index.tsx | 7 +- .../Default => ui/src/views/Edit}/index.scss | 2 +- .../Default => ui/src/views/Edit}/index.tsx | 239 +- .../Default => ui/src/views/Edit}/types.ts | 0 .../List/ListHeader}/index.scss | 13 +- .../ui/src/views/List/ListHeader/index.tsx | 162 + .../Default => ui/src/views/List}/index.scss | 2 +- packages/ui/src/views/List/index.tsx | 315 + .../Default => ui/src/views/List}/types.ts | 3 +- pnpm-lock.yaml | 7193 +++++------ scripts/pack-all-to-dest.ts | 3 +- scripts/run-lint-staged.js | 21 + .../src/app/(payload)/api/[...slug]/route.ts | 1 + .../(payload)/api/graphql-playground/route.ts | 1 + .../_template/src/app/(payload)/layout.tsx | 18 +- .../src/app/(payload)/api/[...slug]/route.ts | 1 + .../(payload)/api/graphql-playground/route.ts | 1 + templates/blank/src/app/(payload)/layout.tsx | 18 +- .../ecommerce/build/2211c49456cd07331ea9.woff | Bin 0 -> 35168 bytes .../build/40ad7515b8674bb854a1.woff2 | Bin 0 -> 23928 bytes .../ecommerce/build/4d8845b830f4e8e2affb.png | Bin 0 -> 4035 bytes .../build/51922ceb71da289688d3.woff2 | Bin 0 -> 23104 bytes .../build/522443364fda49e9e0ed.woff2 | Bin 0 -> 19816 bytes .../build/5b718d9772de251a8c0a.woff2 | Bin 0 -> 61412 bytes .../ecommerce/build/787999a6af6a17efbc7c.woff | Bin 0 -> 24560 bytes .../ecommerce/build/78b8935fb481e11c92ce.woff | Bin 0 -> 24164 bytes .../ecommerce/build/8b4ddd0d08500553efde.woff | Bin 0 -> 34288 bytes .../ecommerce/build/8f612153248094525d9d.woff | Bin 0 -> 24228 bytes .../build/9c7dfd0036f7bd24b053.woff2 | Bin 0 -> 23580 bytes .../build/a1cfdc5b5250b7c4b481.woff2 | Bin 0 -> 19780 bytes .../ecommerce/build/d7aeda9e48ce098e7b48.woff | Bin 0 -> 87996 bytes .../build/e009f21405b4d7e89367.woff2 | Bin 0 -> 20028 bytes .../ecommerce/build/e7caa9e17af6ac87d182.woff | Bin 0 -> 35080 bytes .../ecommerce/build/ebcc1430049fddb274f8.svg | 15 + .../ecommerce/build/efe8f6a3b46446cc9135.woff | Bin 0 -> 24288 bytes .../build/f53bb8d4b29adc903703.woff2 | Bin 0 -> 19844 bytes templates/ecommerce/build/index.html | 1 + .../build/main.51c80d24ccfaac193de5.js | 81 + .../build/main.e12995fc588855befd15.js | 81 + .../build/styles.899181a99ed223bddf0f.css | 804 ++ .../build/styles.b10f95f970b72c88b1b2.js | 1 + .../build/styles.f062b7dde7a81931bf45.js | 1 + templates/ecommerce/media/image-1.jpg | Bin 0 -> 93586 bytes templates/ecommerce/media/image-2.jpg | Bin 0 -> 349328 bytes templates/ecommerce/media/image-3.jpg | Bin 0 -> 248185 bytes templates/ecommerce/media/image-4.jpg | Bin 0 -> 93586 bytes templates/ecommerce/media/image-5.jpg | Bin 0 -> 349328 bytes templates/ecommerce/media/image-6.jpg | Bin 0 -> 248185 bytes .../src/app/(payload)/api/[...slug]/route.ts | 1 + .../(payload)/api/graphql-playground/route.ts | 1 + .../src/app/(payload)/layout.tsx | 21 +- templates/website/pnpm-lock.yaml | 10536 +++++++--------- .../src/app/(payload)/admin/importMap.js | 65 +- .../src/app/(payload)/api/[...slug]/route.ts | 1 + .../(payload)/api/graphql-playground/route.ts | 1 + .../website/src/app/(payload)/layout.tsx | 18 +- .../website/src/fields/slug/SlugComponent.tsx | 28 +- .../src/app/(payload)/api/[...slug]/route.ts | 1 + .../(payload)/api/graphql-playground/route.ts | 1 + .../src/app/(payload)/layout.tsx | 18 +- .../src/app/(payload)/api/[...slug]/route.ts | 1 + .../(payload)/api/graphql-playground/route.ts | 1 + .../src/app/(payload)/layout.tsx | 18 +- .../src/app/(payload)/api/[...slug]/route.ts | 1 + .../(payload)/api/graphql-playground/route.ts | 1 + .../src/app/(payload)/layout.tsx | 18 +- .../src/app/(payload)/api/[...slug]/route.ts | 1 + .../(payload)/api/graphql-playground/route.ts | 1 + .../src/app/(payload)/layout.tsx | 18 +- .../collections/Posts/MyClientField.tsx | 24 +- .../collections/Posts/MyServerField.tsx | 29 +- test/_community/collections/Posts/index.ts | 64 +- test/_community/config.ts | 118 +- test/_community/e2e.spec.ts | 4 +- test/_community/payload-types.ts | 159 +- test/access-control/e2e.spec.ts | 17 +- test/access-control/payload-types.ts | 433 + .../(payload)/[[...segments]]/not-found.tsx | 8 +- .../app/(payload)/[[...segments]]/page.tsx | 8 +- test/admin-root/app/(payload)/layout.tsx | 18 +- test/admin-root/e2e.spec.ts | 9 + test/admin-root/payload-types.ts | 82 + .../CustomFields/FieldDescription/index.tsx | 5 +- .../CustomFields/fields/Select/index.tsx | 10 +- .../CustomFields/fields/Text/FieldClient.tsx | 9 + test/admin/collections/CustomFields/index.ts | 1 + test/admin/collections/Posts.ts | 37 +- .../components/AfterDashboardClient/index.tsx | 21 +- test/admin/components/Banner/index.tsx | 19 + test/admin/components/CustomCell/index.tsx | 9 +- test/admin/components/CustomHeader/index.tsx | 4 +- test/admin/components/TestComponent.tsx | 6 +- .../components/ViewDescription/index.tsx | 15 +- test/admin/components/graphics/Icon.tsx | 12 + test/admin/components/graphics/Logo.tsx | 12 + test/admin/config.ts | 5 +- test/admin/e2e/1/e2e.spec.ts | 49 +- test/admin/e2e/2/e2e.spec.ts | 43 +- test/admin/payload-types.ts | 333 + test/admin/seed.ts | 18 +- .../admin/[[...segments]]/not-found.tsx | 8 +- .../(payload)/admin/[[...segments]]/page.tsx | 8 +- test/app/(payload)/layout.tsx | 18 +- test/array-update/payload-types.ts | 106 + test/auth/config.ts | 52 +- test/auth/e2e.spec.ts | 44 +- test/auth/payload-types.ts | 173 + test/collections-graphql/int.spec.ts | 45 +- test/collections-graphql/payload-types.ts | 289 + test/collections-rest/int.spec.ts | 41 +- test/collections-rest/payload-types.ts | 226 + test/config/payload-types.ts | 117 + test/custom-graphql/payload-types.ts | 78 + test/dataloader/payload-types.ts | 168 + test/dev.ts | 16 +- test/email-nodemailer/payload-types.ts | 78 + test/email-resend/payload-types.ts | 78 + test/email/payload-types.ts | 163 + test/endpoints/payload-types.ts | 129 + test/field-error-states/payload-types.ts | 322 + test/field-perf/payload-types.ts | 112 + .../PrePopulateFieldUI/index.tsx | 7 +- test/fields-relationship/config.ts | 7 +- test/fields-relationship/e2e.spec.ts | 4 +- test/fields-relationship/payload-types.ts | 299 + test/fields/collections/Array/e2e.spec.ts | 8 +- test/fields/collections/Array/index.ts | 9 + .../components/AddCustomBlocks/index.tsx | 51 +- test/fields/collections/Blocks/e2e.spec.ts | 8 +- test/fields/collections/Blocks/index.ts | 4 +- test/fields/collections/Blocks/shared.ts | 5 - .../CustomLabel/getCustomLabel.tsx | 4 +- test/fields/collections/Collapsible/index.ts | 10 +- test/fields/collections/Date/e2e.spec.ts | 8 +- test/fields/collections/Email/CustomLabel.tsx | 9 +- test/fields/collections/Email/e2e.spec.ts | 8 +- .../Lexical/e2e/blocks/e2e.spec.ts | 41 +- .../collections/Lexical/e2e/main/e2e.spec.ts | 83 +- test/fields/collections/Lexical/index.ts | 7 +- test/fields/collections/Number/e2e.spec.ts | 8 +- test/fields/collections/Point/e2e.spec.ts | 8 +- .../collections/Relationship/e2e.spec.ts | 8 +- test/fields/collections/RichText/e2e.spec.ts | 22 +- test/fields/collections/Tabs/e2e.spec.ts | 8 +- .../collections/Text/CustomDescription.tsx | 5 + test/fields/collections/Text/e2e.spec.ts | 8 +- test/fields/collections/UI/UICustomClient.tsx | 14 +- test/fields/collections/Upload/e2e.spec.ts | 8 +- test/fields/collections/Upload/index.ts | 8 +- .../collections/UploadRestricted/e2e.spec.ts | 8 +- test/fields/collections/Uploads3/index.ts | 8 +- test/fields/e2e.spec.ts | 16 +- test/fields/int.spec.ts | 26 +- test/fields/payload-types.ts | 1484 ++- test/fields/seed.ts | 15 - test/fields/slugs.ts | 54 +- test/globals/payload-types.ts | 145 + test/graphql-schema-gen/payload-types.ts | 171 + test/helpers.ts | 45 +- test/helpers/e2e/navigateToDoc.ts | 7 +- test/helpers/e2e/navigateToFirstCellLink.ts | 8 - test/helpers/initPayloadE2E.ts | 71 - test/helpers/initPayloadE2ENoConfig.ts | 51 +- test/helpers/reInit.ts | 1 + test/helpers/reInitializeDB.ts | 52 +- test/helpers/seed.ts | 24 +- test/helpers/startMemoryDB.ts | 3 +- .../waitForAutoSaveToRunAndComplete.ts | 23 + test/hooks/payload-types.ts | 242 + test/i18n/e2e.spec.ts | 5 +- test/i18n/payload-types.ts | 97 + test/jest-spec-reporter.cjs | 21 + .../admin/[[...segments]]/not-found.tsx | 14 +- .../(payload)/admin/[[...segments]]/page.tsx | 10 +- test/live-preview/app/(payload)/layout.tsx | 17 +- test/live-preview/e2e.spec.ts | 7 +- test/live-preview/next-env.d.ts | 2 +- test/live-preview/payload-types.ts | 627 + test/localization-rtl/payload-types.ts | 95 + test/localization/config.ts | 15 - test/localization/payload-types.ts | 539 +- test/locked-documents/e2e.spec.ts | 4 +- test/locked-documents/payload-types.ts | 119 + test/login-with-username/payload-types.ts | 106 + test/migrations-cli/payload-types.ts | 78 + test/nested-fields/payload-types.ts | 182 + test/next.config.mjs | 6 +- test/payload-cloud/payload-types.ts | 126 + test/playwright.config.ts | 9 +- test/plugin-cloud-storage/payload-types.ts | 149 + test/plugin-form-builder/payload-types.ts | 301 + test/plugin-nested-docs/e2e.spec.ts | 7 +- test/plugin-nested-docs/payload-types.ts | 129 + test/plugin-redirects/payload-types.ts | 118 + test/plugin-search/payload-types.ts | 131 + test/plugin-sentry/payload-types.ts | 69 + test/plugin-seo/e2e.spec.ts | 3 +- test/plugin-seo/payload-types.ts | 168 +- test/plugin-stripe/payload-types.ts | 145 + test/plugins/payload-types.ts | 93 + test/queues/e2e.spec.ts | 65 - test/queues/payload-types.ts | 111 + test/relationships/payload-types.ts | 329 + test/runE2E.ts | 44 +- test/sort/payload-types.ts | 119 + test/storage-azure/payload-types.ts | 149 + test/storage-gcs/payload-types.ts | 149 + test/storage-s3/payload-types.ts | 149 + test/tsconfig.json | 4 +- test/uploads/.gitignore | 1 + test/uploads/e2e.spec.ts | 138 +- test/uploads/payload-types.ts | 1323 ++ test/versions/e2e.spec.ts | 32 +- .../CustomSaveButton/index.module.scss | 42 +- .../elements/CustomSaveButton/index.tsx | 8 +- test/versions/payload-types.ts | 308 +- tsconfig.json | 11 +- 657 files changed, 34245 insertions(+), 21057 deletions(-) create mode 100644 packages/db-mongodb/src/countGlobalVersions.ts create mode 100644 packages/db-mongodb/src/countVersions.ts create mode 100644 packages/drizzle/src/countGlobalVersions.ts create mode 100644 packages/drizzle/src/countVersions.ts delete mode 100644 packages/next/src/elements/EmailAndUsername/index.tsx create mode 100644 packages/next/src/layouts/Root/NestProviders.tsx delete mode 100644 packages/next/src/routes/rest/buildFormState.ts create mode 100644 packages/next/src/utilities/getClientConfig.ts create mode 100644 packages/next/src/utilities/handleServerFunctions.ts create mode 100644 packages/next/src/views/Document/getDocPreferences.ts create mode 100644 packages/next/src/views/Document/getDocumentData.ts delete mode 100644 packages/next/src/views/Document/getDocumentData.tsx create mode 100644 packages/next/src/views/Document/getIsLocked.ts create mode 100644 packages/next/src/views/Document/getVersions.ts create mode 100644 packages/next/src/views/Document/handleServerFunction.tsx create mode 100644 packages/next/src/views/Document/renderDocumentSlots.tsx delete mode 100644 packages/next/src/views/Edit/index.client.tsx delete mode 100644 packages/next/src/views/List/Default/index.tsx create mode 100644 packages/next/src/views/List/handleServerFunction.tsx create mode 100644 packages/next/src/views/List/renderListViewSlots.tsx create mode 100644 packages/payload/src/admin/fields/UI.ts create mode 100644 packages/payload/src/admin/functions/index.ts create mode 100644 packages/payload/src/collections/operations/countVersions.ts create mode 100644 packages/payload/src/collections/operations/local/countVersions.ts create mode 100644 packages/payload/src/globals/operations/countGlobalVersions.ts create mode 100644 packages/payload/src/globals/operations/local/countGlobalVersions.ts create mode 100644 packages/payload/src/utilities/formatErrors.ts delete mode 100644 packages/payload/src/utilities/mergeListSearchAndWhere.ts delete mode 100644 packages/richtext-lexical/src/cell/index.tsx create mode 100644 packages/richtext-lexical/src/cell/rscEntry.tsx create mode 100644 packages/richtext-lexical/src/exports/server/rsc.ts create mode 100644 packages/richtext-lexical/src/field/rscEntry.tsx delete mode 100644 packages/richtext-lexical/src/utilities/generateComponentMap.tsx create mode 100644 packages/richtext-lexical/src/utilities/initLexicalFeatures.ts delete mode 100644 packages/richtext-slate/src/cell/index.tsx create mode 100644 packages/richtext-slate/src/cell/rscEntry.tsx create mode 100644 packages/richtext-slate/src/exports/server/rsc.ts create mode 100644 packages/richtext-slate/src/field/rscEntry.tsx delete mode 100644 packages/richtext-slate/src/generateComponentMap.tsx create mode 100644 packages/richtext-slate/src/utilities/SlatePropsProvider.tsx create mode 100644 packages/ui/src/elements/DocumentDrawer/DrawerHeader/index.scss create mode 100644 packages/ui/src/elements/DocumentDrawer/DrawerHeader/index.tsx create mode 100644 packages/ui/src/elements/DocumentDrawer/Provider.tsx rename packages/{next => ui}/src/elements/DocumentLocked/index.scss (100%) rename packages/{next => ui}/src/elements/DocumentLocked/index.tsx (91%) rename packages/{next => ui}/src/elements/DocumentTakeOver/index.scss (100%) rename packages/{next => ui}/src/elements/DocumentTakeOver/index.tsx (90%) create mode 100644 packages/ui/src/elements/EmailAndUsername/index.tsx rename packages/{next => ui}/src/elements/LeaveWithoutSaving/index.scss (100%) rename packages/{next => ui}/src/elements/LeaveWithoutSaving/index.tsx (88%) rename packages/{next => ui}/src/elements/LeaveWithoutSaving/usePreventLeave.tsx (100%) create mode 100644 packages/ui/src/elements/ListDrawer/Provider.tsx delete mode 100644 packages/ui/src/elements/ListHeader/index.scss delete mode 100644 packages/ui/src/elements/ListHeader/index.tsx create mode 100644 packages/ui/src/elements/RelationshipTable/Pagination.tsx delete mode 100644 packages/ui/src/elements/RelationshipTable/TableWrapper.tsx create mode 100644 packages/ui/src/elements/RenderComponent/index.tsx create mode 100644 packages/ui/src/elements/RenderCustomComponent/index.tsx create mode 100644 packages/ui/src/elements/RenderIfInViewport/index.tsx create mode 100644 packages/ui/src/elements/RenderServerComponent/index.tsx delete mode 100644 packages/ui/src/elements/Table/RenderCell.tsx delete mode 100644 packages/ui/src/elements/Table/TableCellProvider/index.tsx create mode 100644 packages/ui/src/elements/TableColumns/RenderDefaultCell/index.scss create mode 100644 packages/ui/src/elements/TableColumns/RenderDefaultCell/index.tsx create mode 100644 packages/ui/src/elements/WhereBuilder/Condition/DefaultFilter/index.tsx create mode 100644 packages/ui/src/exports/rsc/index.ts delete mode 100644 packages/ui/src/forms/FieldPropsProvider/index.tsx create mode 100644 packages/ui/src/forms/Form/reduceToSerializableFields.ts delete mode 100644 packages/ui/src/forms/buildStateFromSchema/index.tsx rename packages/ui/src/forms/{buildStateFromSchema => fieldSchemasToFormState}/addFieldStatePromise.ts (69%) rename packages/ui/src/forms/{buildStateFromSchema => fieldSchemasToFormState}/calculateDefaultValues/index.ts (100%) rename packages/ui/src/forms/{buildStateFromSchema => fieldSchemasToFormState}/calculateDefaultValues/iterateFields.ts (99%) rename packages/ui/src/forms/{buildStateFromSchema => fieldSchemasToFormState}/calculateDefaultValues/promise.ts (99%) rename packages/ui/src/forms/{buildStateFromSchema/buildStateFromSchema.spec.js => fieldSchemasToFormState/fieldSchemasToFormState.spec.js} (77%) rename packages/ui/src/forms/{buildStateFromSchema => fieldSchemasToFormState}/getFilterOptionsQuery.ts (100%) create mode 100644 packages/ui/src/forms/fieldSchemasToFormState/index.tsx rename packages/ui/src/forms/{buildStateFromSchema => fieldSchemasToFormState}/iterateFields.ts (55%) create mode 100644 packages/ui/src/forms/fieldSchemasToFormState/renderField.tsx create mode 100644 packages/ui/src/forms/fieldSchemasToFormState/types.ts delete mode 100644 packages/ui/src/providers/Actions/SetViewActions/index.tsx delete mode 100644 packages/ui/src/providers/Config/RenderComponent.tsx delete mode 100644 packages/ui/src/providers/Config/createClientConfig/collections.tsx delete mode 100644 packages/ui/src/providers/Config/createClientConfig/fields.tsx delete mode 100644 packages/ui/src/providers/Config/createClientConfig/getComponent.ts delete mode 100644 packages/ui/src/providers/Config/createClientConfig/getCreateMappedComponent.tsx delete mode 100644 packages/ui/src/providers/Config/createClientConfig/globals.tsx delete mode 100644 packages/ui/src/providers/Config/createClientConfig/index.tsx create mode 100644 packages/ui/src/providers/DocumentInfo/useGetDocPermissions.tsx delete mode 100644 packages/ui/src/providers/ListInfo/index.tsx create mode 100644 packages/ui/src/providers/ServerFunctions/index.tsx delete mode 100644 packages/ui/src/utilities/buildFieldSchemaMap/types.ts create mode 100644 packages/ui/src/utilities/buildTableState.ts delete mode 100644 packages/ui/src/utilities/getFormState.ts create mode 100644 packages/ui/src/utilities/handleFormStateLocking.ts create mode 100644 packages/ui/src/utilities/mergeListSearchAndWhere.ts create mode 100644 packages/ui/src/utilities/renderTable.tsx rename packages/{next/src/views/Edit/Default => ui/src/views/Edit}/Auth/APIKey.tsx (84%) rename packages/{next/src/views/Edit/Default => ui/src/views/Edit}/Auth/index.scss (97%) rename packages/{next/src/views/Edit/Default => ui/src/views/Edit}/Auth/index.tsx (81%) rename packages/{next/src/views/Edit/Default => ui/src/views/Edit}/Auth/types.ts (90%) rename packages/{next/src/views/Edit/Default => ui/src/views/Edit}/SetDocumentStepNav/index.tsx (81%) rename packages/{next/src/views/Edit/Default => ui/src/views/Edit}/SetDocumentTitle/index.tsx (81%) rename packages/{next/src/views/Edit/Default => ui/src/views/Edit}/index.scss (89%) rename packages/{next/src/views/Edit/Default => ui/src/views/Edit}/index.tsx (75%) rename packages/{next/src/views/Edit/Default => ui/src/views/Edit}/types.ts (100%) rename packages/ui/src/{elements/ListDrawer => views/List/ListHeader}/index.scss (91%) create mode 100644 packages/ui/src/views/List/ListHeader/index.tsx rename packages/{next/src/views/List/Default => ui/src/views/List}/index.scss (98%) create mode 100644 packages/ui/src/views/List/index.tsx rename packages/{next/src/views/List/Default => ui/src/views/List}/types.ts (83%) create mode 100644 scripts/run-lint-staged.js create mode 100644 templates/ecommerce/build/2211c49456cd07331ea9.woff create mode 100644 templates/ecommerce/build/40ad7515b8674bb854a1.woff2 create mode 100644 templates/ecommerce/build/4d8845b830f4e8e2affb.png create mode 100644 templates/ecommerce/build/51922ceb71da289688d3.woff2 create mode 100644 templates/ecommerce/build/522443364fda49e9e0ed.woff2 create mode 100644 templates/ecommerce/build/5b718d9772de251a8c0a.woff2 create mode 100644 templates/ecommerce/build/787999a6af6a17efbc7c.woff create mode 100644 templates/ecommerce/build/78b8935fb481e11c92ce.woff create mode 100644 templates/ecommerce/build/8b4ddd0d08500553efde.woff create mode 100644 templates/ecommerce/build/8f612153248094525d9d.woff create mode 100644 templates/ecommerce/build/9c7dfd0036f7bd24b053.woff2 create mode 100644 templates/ecommerce/build/a1cfdc5b5250b7c4b481.woff2 create mode 100644 templates/ecommerce/build/d7aeda9e48ce098e7b48.woff create mode 100644 templates/ecommerce/build/e009f21405b4d7e89367.woff2 create mode 100644 templates/ecommerce/build/e7caa9e17af6ac87d182.woff create mode 100644 templates/ecommerce/build/ebcc1430049fddb274f8.svg create mode 100644 templates/ecommerce/build/efe8f6a3b46446cc9135.woff create mode 100644 templates/ecommerce/build/f53bb8d4b29adc903703.woff2 create mode 100644 templates/ecommerce/build/index.html create mode 100644 templates/ecommerce/build/main.51c80d24ccfaac193de5.js create mode 100644 templates/ecommerce/build/main.e12995fc588855befd15.js create mode 100644 templates/ecommerce/build/styles.899181a99ed223bddf0f.css create mode 100644 templates/ecommerce/build/styles.b10f95f970b72c88b1b2.js create mode 100644 templates/ecommerce/build/styles.f062b7dde7a81931bf45.js create mode 100644 templates/ecommerce/media/image-1.jpg create mode 100644 templates/ecommerce/media/image-2.jpg create mode 100644 templates/ecommerce/media/image-3.jpg create mode 100644 templates/ecommerce/media/image-4.jpg create mode 100644 templates/ecommerce/media/image-5.jpg create mode 100644 templates/ecommerce/media/image-6.jpg create mode 100644 test/admin/collections/CustomFields/fields/Text/FieldClient.tsx create mode 100644 test/admin/components/Banner/index.tsx create mode 100644 test/admin/components/graphics/Icon.tsx create mode 100644 test/admin/components/graphics/Logo.tsx create mode 100644 test/fields/collections/Text/CustomDescription.tsx delete mode 100644 test/helpers/e2e/navigateToFirstCellLink.ts delete mode 100644 test/helpers/initPayloadE2E.ts create mode 100644 test/helpers/waitForAutoSaveToRunAndComplete.ts delete mode 100644 test/queues/e2e.spec.ts diff --git a/app/(payload)/layout.tsx b/app/(payload)/layout.tsx index 396a5cc75..497fd2e02 100644 --- a/app/(payload)/layout.tsx +++ b/app/(payload)/layout.tsx @@ -1,8 +1,10 @@ /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ -import configPromise from '@payload-config' -import { RootLayout } from '@payloadcms/next/layouts' // import '@payloadcms/ui/styles.css' // Uncomment this line if `@payloadcms/ui` in `tsconfig.json` points to `/ui/dist` instead of `/ui/src` +import type { ServerFunctionClient } from 'payload' + +import config from '@payload-config' +import { handleServerFunctions, RootLayout } from '@payloadcms/next/layouts' import React from 'react' import { importMap } from './admin/importMap.js' @@ -12,8 +14,17 @@ type Args = { children: React.ReactNode } +const serverFunction: ServerFunctionClient = async function (args) { + 'use server' + return handleServerFunctions({ + ...args, + config, + importMap, + }) +} + const Layout = ({ children }: Args) => ( - + {children} ) diff --git a/docs/admin/fields.mdx b/docs/admin/fields.mdx index f13eb6c81..73ac795fc 100644 --- a/docs/admin/fields.mdx +++ b/docs/admin/fields.mdx @@ -228,7 +228,6 @@ The following additional properties are also provided to the `field` prop: | Property | Description | | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`_isPresentational`** | A boolean indicating that the field is purely visual and does not directly affect data or change data shape, i.e. the [UI Field](../fields/ui). | | **`_path`** | A string representing the direct, dynamic path to the field at runtime, i.e. `myGroup.myArray[0].myField`. | | **`_schemaPath`** | A string representing the direct, static path to the [Field Config](../fields/overview), i.e. `myGroup.myArray.myField` | diff --git a/docs/lexical/converters.mdx b/docs/lexical/converters.mdx index 8f4e31178..ca98ba169 100644 --- a/docs/lexical/converters.mdx +++ b/docs/lexical/converters.mdx @@ -370,7 +370,12 @@ const yourEditorState: SerializedEditorState // <= your current editor state her // Import editor state into your headless editor try { - headlessEditor.setEditorState(headlessEditor.parseEditorState(yourEditorState)) // This should commit the editor state immediately + headlessEditor.update( + () => { + headlessEditor.setEditorState(headlessEditor.parseEditorState(yourEditorState)) + }, + { discrete: true }, // This should commit the editor state immediately + ) } catch (e) { logger.error({ err: e }, 'ERROR parsing editor state') } @@ -382,8 +387,6 @@ headlessEditor.getEditorState().read(() => { }) ``` -The `.setEditorState()` function immediately updates your editor state. Thus, there's no need for the `discrete: true` flag when reading the state afterward. - ## Lexical => Plain Text Export content from the Lexical editor into plain text using these steps: @@ -401,8 +404,13 @@ const yourEditorState: SerializedEditorState // <= your current editor state her // Import editor state into your headless editor try { - headlessEditor.setEditorState(headlessEditor.parseEditorState(yourEditorState)) // This should commit the editor state immediately -} catch (e) { + headlessEditor.update( + () => { + headlessEditor.setEditorState(headlessEditor.parseEditorState(yourEditorState)) + }, + { discrete: true }, // This should commit the editor state immediately + ) + } catch (e) { logger.error({ err: e }, 'ERROR parsing editor state') } diff --git a/examples/custom-components/src/app/(payload)/layout.tsx b/examples/custom-components/src/app/(payload)/layout.tsx index d2cf5ab1c..af755c33b 100644 --- a/examples/custom-components/src/app/(payload)/layout.tsx +++ b/examples/custom-components/src/app/(payload)/layout.tsx @@ -1,6 +1,6 @@ /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ -import configPromise from '@payload-config' +import config from '@payload-config' import '@payloadcms/next/css' import { RootLayout } from '@payloadcms/next/layouts' import React from 'react' @@ -13,7 +13,7 @@ type Args = { } const Layout = ({ children }: Args) => ( - + {children} ) diff --git a/examples/hierarchy/src/app/(payload)/layout.tsx b/examples/hierarchy/src/app/(payload)/layout.tsx index 7997f272f..cfcc0bcb0 100644 --- a/examples/hierarchy/src/app/(payload)/layout.tsx +++ b/examples/hierarchy/src/app/(payload)/layout.tsx @@ -1,8 +1,8 @@ /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ +/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ import configPromise from '@payload-config' import '@payloadcms/next/css' import { RootLayout } from '@payloadcms/next/layouts' -/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ import React from 'react' import './custom.scss' diff --git a/examples/multi-tenant/src/app/(payload)/layout.tsx b/examples/multi-tenant/src/app/(payload)/layout.tsx index d2cf5ab1c..af755c33b 100644 --- a/examples/multi-tenant/src/app/(payload)/layout.tsx +++ b/examples/multi-tenant/src/app/(payload)/layout.tsx @@ -1,6 +1,6 @@ /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ -import configPromise from '@payload-config' +import config from '@payload-config' import '@payloadcms/next/css' import { RootLayout } from '@payloadcms/next/layouts' import React from 'react' @@ -13,7 +13,7 @@ type Args = { } const Layout = ({ children }: Args) => ( - + {children} ) diff --git a/next.config.mjs b/next.config.mjs index 3d865ff7f..418a13b7d 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -19,6 +19,11 @@ const config = withBundleAnalyzer( typescript: { ignoreBuildErrors: true, }, + experimental: { + serverActions: { + bodySizeLimit: '5mb', + }, + }, env: { PAYLOAD_CORE_DEV: 'true', ROOT_DIR: path.resolve(dirname), diff --git a/package.json b/package.json index 1a8a22b0d..37990302b 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "dev:generate-importmap": "pnpm runts ./test/generateImportMap.ts", "dev:generate-types": "pnpm runts ./test/generateTypes.ts", "dev:postgres": "cross-env PAYLOAD_DATABASE=postgres pnpm runts ./test/dev.ts", + "dev:prod": "cross-env NODE_OPTIONS=--no-deprecation tsx ./test/dev.ts --prod", "dev:vercel-postgres": "cross-env PAYLOAD_DATABASE=vercel-postgres pnpm runts ./test/dev.ts", "devsafe": "node ./scripts/delete-recursively.js '**/.next' && pnpm dev", "docker:restart": "pnpm docker:stop --remove-orphans && pnpm docker:start", @@ -65,12 +66,12 @@ "docker:stop": "docker compose -f packages/plugin-cloud-storage/docker-compose.yml down", "force:build": "pnpm run build:core:force", "lint": "turbo run lint --concurrency 1 --continue", - "lint-staged": "lint-staged", + "lint-staged": "node ./scripts/run-lint-staged.js", "lint:fix": "turbo run lint:fix --concurrency 1 --continue", "obliterate-playwright-cache-macos": "rm -rf ~/Library/Caches/ms-playwright && find /System/Volumes/Data/private/var/folders -type d -name 'playwright*' -exec rm -rf {} +", "prepare": "husky", - "prepare-run-test-against-prod": "pnpm bf && rm -rf test/packed && rm -rf test/node_modules && rm -f test/pnpm-lock.yaml && pnpm run script:pack --all --no-build --dest test/packed && pnpm runts test/setupProd.ts && cd test && pnpm i --ignore-workspace && cd ..", - "prepare-run-test-against-prod:ci": "rm -rf test/node_modules && rm -f test/pnpm-lock.yaml && pnpm run script:pack --all --no-build --dest test/packed && pnpm runts test/setupProd.ts && cd test && pnpm i --ignore-workspace && cd ..", + "prepare-run-test-against-prod": "pnpm bf && rm -rf test/packed && rm -rf test/node_modules && rm -rf app && rm -f test/pnpm-lock.yaml && pnpm run script:pack --all --no-build --dest test/packed && pnpm runts test/setupProd.ts && cd test && pnpm i --ignore-workspace && cd ..", + "prepare-run-test-against-prod:ci": "rm -rf test/node_modules && rm -rf app && rm -f test/pnpm-lock.yaml && pnpm run script:pack --all --no-build --dest test/packed && pnpm runts test/setupProd.ts && cd test && pnpm i --ignore-workspace && cd ..", "reinstall": "pnpm clean:all && pnpm install", "release:alpha": "pnpm runts ./scripts/release.ts --bump prerelease --tag alpha", "release:beta": "pnpm runts ./scripts/release.ts --bump prerelease --tag beta", diff --git a/packages/db-mongodb/src/countGlobalVersions.ts b/packages/db-mongodb/src/countGlobalVersions.ts new file mode 100644 index 000000000..ea95c60f2 --- /dev/null +++ b/packages/db-mongodb/src/countGlobalVersions.ts @@ -0,0 +1,48 @@ +import type { QueryOptions } from 'mongoose' +import type { CountGlobalVersions, PayloadRequest } from 'payload' + +import { flattenWhereToOperators } from 'payload' + +import type { MongooseAdapter } from './index.js' + +import { withSession } from './withSession.js' + +export const countGlobalVersions: CountGlobalVersions = async function countGlobalVersions( + this: MongooseAdapter, + { global, locale, req = {} as PayloadRequest, where }, +) { + const Model = this.versions[global] + const options: QueryOptions = await withSession(this, req) + + let hasNearConstraint = false + + if (where) { + const constraints = flattenWhereToOperators(where) + hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near')) + } + + const query = await Model.buildQuery({ + locale, + payload: this.payload, + where, + }) + + // useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters. + const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0 + + if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) { + // Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding + // a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents, + // which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses + // the correct indexed field + options.hint = { + _id: 1, + } + } + + const result = await Model.countDocuments(query, options) + + return { + totalDocs: result, + } +} diff --git a/packages/db-mongodb/src/countVersions.ts b/packages/db-mongodb/src/countVersions.ts new file mode 100644 index 000000000..9b9fb6a6a --- /dev/null +++ b/packages/db-mongodb/src/countVersions.ts @@ -0,0 +1,48 @@ +import type { QueryOptions } from 'mongoose' +import type { CountVersions, PayloadRequest } from 'payload' + +import { flattenWhereToOperators } from 'payload' + +import type { MongooseAdapter } from './index.js' + +import { withSession } from './withSession.js' + +export const countVersions: CountVersions = async function countVersions( + this: MongooseAdapter, + { collection, locale, req = {} as PayloadRequest, where }, +) { + const Model = this.versions[collection] + const options: QueryOptions = await withSession(this, req) + + let hasNearConstraint = false + + if (where) { + const constraints = flattenWhereToOperators(where) + hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near')) + } + + const query = await Model.buildQuery({ + locale, + payload: this.payload, + where, + }) + + // useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters. + const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0 + + if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) { + // Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding + // a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents, + // which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses + // the correct indexed field + options.hint = { + _id: 1, + } + } + + const result = await Model.countDocuments(query, options) + + return { + totalDocs: result, + } +} diff --git a/packages/db-mongodb/src/index.ts b/packages/db-mongodb/src/index.ts index b49d90f65..7aa0f75d7 100644 --- a/packages/db-mongodb/src/index.ts +++ b/packages/db-mongodb/src/index.ts @@ -12,6 +12,8 @@ import type { CollectionModel, GlobalModel, MigrateDownArgs, MigrateUpArgs } fro import { connect } from './connect.js' import { count } from './count.js' +import { countGlobalVersions } from './countGlobalVersions.js' +import { countVersions } from './countVersions.js' import { create } from './create.js' import { createGlobal } from './createGlobal.js' import { createGlobalVersion } from './createGlobalVersion.js' @@ -154,7 +156,6 @@ export function mongooseAdapter({ collections: {}, connection: undefined, connectOptions: connectOptions || {}, - count, disableIndexHints, globals: undefined, mongoMemoryServer, @@ -166,6 +167,9 @@ export function mongooseAdapter({ beginTransaction: transactionOptions === false ? defaultBeginTransaction() : beginTransaction, commitTransaction, connect, + count, + countGlobalVersions, + countVersions, create, createGlobal, createGlobalVersion, diff --git a/packages/db-mongodb/src/utilities/buildJoinAggregation.ts b/packages/db-mongodb/src/utilities/buildJoinAggregation.ts index 5cec7a529..f7929b1cf 100644 --- a/packages/db-mongodb/src/utilities/buildJoinAggregation.ts +++ b/packages/db-mongodb/src/utilities/buildJoinAggregation.ts @@ -1,8 +1,6 @@ import type { PipelineStage } from 'mongoose' import type { CollectionSlug, JoinQuery, SanitizedCollectionConfig, Where } from 'payload' -import { combineQueries } from 'payload' - import type { MongooseAdapter } from '../index.js' import { buildSortParam } from '../queries/buildSortParam.js' @@ -60,11 +58,11 @@ export const buildJoinAggregation = async ({ for (const join of joinConfig[slug]) { const joinModel = adapter.collections[join.field.collection] - if (projection && !projection[join.schemaPath]) { + if (projection && !projection[join.joinPath]) { continue } - if (joins?.[join.schemaPath] === false) { + if (joins?.[join.joinPath] === false) { continue } @@ -72,7 +70,7 @@ export const buildJoinAggregation = async ({ limit: limitJoin = join.field.defaultLimit ?? 10, sort: sortJoin = join.field.defaultSort || collectionConfig.defaultSort, where: whereJoin, - } = joins?.[join.schemaPath] || {} + } = joins?.[join.joinPath] || {} const sort = buildSortParam({ config: adapter.payload.config, @@ -105,7 +103,7 @@ export const buildJoinAggregation = async ({ if (adapter.payload.config.localization && locale === 'all') { adapter.payload.config.localization.localeCodes.forEach((code) => { - const as = `${versions ? `version.${join.schemaPath}` : join.schemaPath}${code}` + const as = `${versions ? `version.${join.joinPath}` : join.joinPath}${code}` aggregate.push( { @@ -146,7 +144,7 @@ export const buildJoinAggregation = async ({ } else { const localeSuffix = join.field.localized && adapter.payload.config.localization && locale ? `.${locale}` : '' - const as = `${versions ? `version.${join.schemaPath}` : join.schemaPath}${localeSuffix}` + const as = `${versions ? `version.${join.joinPath}` : join.joinPath}${localeSuffix}` aggregate.push( { diff --git a/packages/db-mongodb/src/utilities/handleError.ts b/packages/db-mongodb/src/utilities/handleError.ts index d28d19af6..259f95f5d 100644 --- a/packages/db-mongodb/src/utilities/handleError.ts +++ b/packages/db-mongodb/src/utilities/handleError.ts @@ -19,8 +19,8 @@ export const handleError = ({ collection, errors: [ { - field: Object.keys(error.keyValue)[0], message: req.t('error:valueMustBeUnique'), + path: Object.keys(error.keyValue)[0], }, ], global, diff --git a/packages/db-postgres/src/index.ts b/packages/db-postgres/src/index.ts index 473436021..9d1271d48 100644 --- a/packages/db-postgres/src/index.ts +++ b/packages/db-postgres/src/index.ts @@ -4,6 +4,8 @@ import { beginTransaction, commitTransaction, count, + countGlobalVersions, + countVersions, create, createGlobal, createGlobalVersion, @@ -126,6 +128,8 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj convertPathToJSONTraversal, count, countDistinct, + countGlobalVersions, + countVersions, create, createGlobal, createGlobalVersion, diff --git a/packages/db-sqlite/src/index.ts b/packages/db-sqlite/src/index.ts index 12b6420b0..320a2326e 100644 --- a/packages/db-sqlite/src/index.ts +++ b/packages/db-sqlite/src/index.ts @@ -5,6 +5,8 @@ import { beginTransaction, commitTransaction, count, + countGlobalVersions, + countVersions, create, createGlobal, createGlobalVersion, @@ -114,6 +116,8 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj { convertPathToJSONTraversal, count, countDistinct, + countGlobalVersions, + countVersions, create, createGlobal, createGlobalVersion, diff --git a/packages/db-vercel-postgres/src/index.ts b/packages/db-vercel-postgres/src/index.ts index e053dccce..757f74f6d 100644 --- a/packages/db-vercel-postgres/src/index.ts +++ b/packages/db-vercel-postgres/src/index.ts @@ -4,6 +4,8 @@ import { beginTransaction, commitTransaction, count, + countGlobalVersions, + countVersions, create, createGlobal, createGlobalVersion, @@ -127,6 +129,8 @@ export function vercelPostgresAdapter(args: Args = {}): DatabaseAdapterObj slug === global, + ) + + const tableName = this.tableNameMap.get( + `_${toSnakeCase(globalConfig.slug)}${this.versionsSuffix}`, + ) + + const db = this.sessions[await req?.transactionID]?.db || this.drizzle + + const fields = buildVersionGlobalFields(this.payload.config, globalConfig) + + const { joins, where } = buildQuery({ + adapter: this, + fields, + locale, + tableName, + where: whereArg, + }) + + const countResult = await this.countDistinct({ + db, + joins, + tableName, + where, + }) + + return { totalDocs: countResult } +} diff --git a/packages/drizzle/src/countVersions.ts b/packages/drizzle/src/countVersions.ts new file mode 100644 index 000000000..c4017acfa --- /dev/null +++ b/packages/drizzle/src/countVersions.ts @@ -0,0 +1,40 @@ +import type { CountVersions, SanitizedCollectionConfig } from 'payload' + +import { buildVersionCollectionFields } from 'payload' +import toSnakeCase from 'to-snake-case' + +import type { DrizzleAdapter } from './types.js' + +import buildQuery from './queries/buildQuery.js' + +export const countVersions: CountVersions = async function countVersions( + this: DrizzleAdapter, + { collection, locale, req, where: whereArg }, +) { + const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config + + const tableName = this.tableNameMap.get( + `_${toSnakeCase(collectionConfig.slug)}${this.versionsSuffix}`, + ) + + const db = this.sessions[await req?.transactionID]?.db || this.drizzle + + const fields = buildVersionCollectionFields(this.payload.config, collectionConfig) + + const { joins, where } = buildQuery({ + adapter: this, + fields, + locale, + tableName, + where: whereArg, + }) + + const countResult = await this.countDistinct({ + db, + joins, + tableName, + where, + }) + + return { totalDocs: countResult } +} diff --git a/packages/drizzle/src/index.ts b/packages/drizzle/src/index.ts index d31b0226e..6c72222f7 100644 --- a/packages/drizzle/src/index.ts +++ b/packages/drizzle/src/index.ts @@ -1,4 +1,6 @@ export { count } from './count.js' +export { countGlobalVersions } from './countGlobalVersions.js' +export { countVersions } from './countVersions.js' export { create } from './create.js' export { createGlobal } from './createGlobal.js' export { createGlobalVersion } from './createGlobalVersion.js' diff --git a/packages/drizzle/src/upsertRow/index.ts b/packages/drizzle/src/upsertRow/index.ts index 77c3448c0..fa09fecc2 100644 --- a/packages/drizzle/src/upsertRow/index.ts +++ b/packages/drizzle/src/upsertRow/index.ts @@ -391,8 +391,8 @@ export const upsertRow = async | TypeWithID>( id, errors: [ { - field: fieldName, message: req.t('error:valueMustBeUnique'), + path: fieldName, }, ], }, diff --git a/packages/next/src/elements/DocumentHeader/Tabs/Tab/index.tsx b/packages/next/src/elements/DocumentHeader/Tabs/Tab/index.tsx index 5a73e6ee2..10a1c0fcb 100644 --- a/packages/next/src/elements/DocumentHeader/Tabs/Tab/index.tsx +++ b/packages/next/src/elements/DocumentHeader/Tabs/Tab/index.tsx @@ -1,7 +1,8 @@ import type { DocumentTabConfig, DocumentTabProps } from 'payload' +import type React from 'react' -import { getCreateMappedComponent, RenderComponent } from '@payloadcms/ui/shared' -import React, { Fragment } from 'react' +import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent' +import { Fragment } from 'react' import './index.scss' import { DocumentTabLink } from './TabLink.js' @@ -59,17 +60,6 @@ export const DocumentTab: React.FC< }) : label - const createMappedComponent = getCreateMappedComponent({ - importMap: payload.importMap, - serverProps: { - i18n, - payload, - permissions, - }, - }) - - const mappedPin = createMappedComponent(Pill, undefined, Pill_Component, 'Pill') - return ( {labelToRender} - {mappedPin && ( + {Pill || Pill_Component ? (   - + - )} + ) : null} ) diff --git a/packages/next/src/elements/DocumentHeader/Tabs/index.tsx b/packages/next/src/elements/DocumentHeader/Tabs/index.tsx index 40896720b..987f193b8 100644 --- a/packages/next/src/elements/DocumentHeader/Tabs/index.tsx +++ b/packages/next/src/elements/DocumentHeader/Tabs/index.tsx @@ -6,7 +6,7 @@ import type { SanitizedGlobalConfig, } from 'payload' -import { getCreateMappedComponent, RenderComponent } from '@payloadcms/ui/shared' +import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent' import React from 'react' import { getCustomViews } from './getCustomViews.js' @@ -80,33 +80,21 @@ export const DocumentTabs: React.FC<{ const { path, tab } = CustomView if (tab.Component) { - const createMappedComponent = getCreateMappedComponent({ - importMap: payload.importMap, - serverProps: { - i18n, - payload, - permissions, - ...props, - key: `tab-custom-${index}`, - path, - }, - }) - - const mappedTab = createMappedComponent( - tab.Component, - undefined, - undefined, - 'tab.Component', - ) - return ( - ) } @@ -121,6 +109,7 @@ export const DocumentTabs: React.FC<{ /> ) } + return null })} diff --git a/packages/next/src/elements/DocumentHeader/Tabs/tabs/VersionsPill/index.tsx b/packages/next/src/elements/DocumentHeader/Tabs/tabs/VersionsPill/index.tsx index 72d61c268..78a0e3d14 100644 --- a/packages/next/src/elements/DocumentHeader/Tabs/tabs/VersionsPill/index.tsx +++ b/packages/next/src/elements/DocumentHeader/Tabs/tabs/VersionsPill/index.tsx @@ -5,14 +5,11 @@ import React from 'react' import { baseClass } from '../../Tab/index.js' export const VersionsPill: React.FC = () => { - const { versions } = useDocumentInfo() + const { versionCount } = useDocumentInfo() - // don't count snapshots - const totalVersions = versions?.docs.filter((version) => !version.snapshot).length || 0 - - if (!versions?.totalDocs) { + if (!versionCount) { return null } - return {totalVersions} + return {versionCount} } diff --git a/packages/next/src/elements/DocumentHeader/index.tsx b/packages/next/src/elements/DocumentHeader/index.tsx index 6d9cfd34e..67e984a2f 100644 --- a/packages/next/src/elements/DocumentHeader/index.tsx +++ b/packages/next/src/elements/DocumentHeader/index.tsx @@ -7,7 +7,7 @@ import type { } from 'payload' import { Gutter, RenderTitle } from '@payloadcms/ui' -import React, { Fragment } from 'react' +import React from 'react' import './index.scss' import { DocumentTabs } from './Tabs/index.js' @@ -16,32 +16,25 @@ const baseClass = `doc-header` export const DocumentHeader: React.FC<{ collectionConfig?: SanitizedCollectionConfig - customHeader?: React.ReactNode globalConfig?: SanitizedGlobalConfig hideTabs?: boolean i18n: I18n payload: Payload permissions: Permissions }> = (props) => { - const { collectionConfig, customHeader, globalConfig, hideTabs, i18n, payload, permissions } = - props + const { collectionConfig, globalConfig, hideTabs, i18n, payload, permissions } = props return ( - {customHeader && customHeader} - {!customHeader && ( - - - {!hideTabs && ( - - )} - + + {!hideTabs && ( + )} ) diff --git a/packages/next/src/elements/EmailAndUsername/index.tsx b/packages/next/src/elements/EmailAndUsername/index.tsx deleted file mode 100644 index a7727566d..000000000 --- a/packages/next/src/elements/EmailAndUsername/index.tsx +++ /dev/null @@ -1,114 +0,0 @@ -'use client' - -import type { FieldPermissions, LoginWithUsernameOptions } from 'payload' - -import { EmailField, RenderFields, TextField, useTranslation } from '@payloadcms/ui' -import { email, username } from 'payload/shared' -import React from 'react' - -type Props = { - readonly loginWithUsername?: false | LoginWithUsernameOptions -} -function EmailFieldComponent(props: Props) { - const { loginWithUsername } = props - const { t } = useTranslation() - - const requireEmail = !loginWithUsername || (loginWithUsername && loginWithUsername.requireEmail) - const showEmailField = - !loginWithUsername || loginWithUsername?.requireEmail || loginWithUsername?.allowEmailLogin - - if (showEmailField) { - return ( - - ) - } - - return null -} - -function UsernameFieldComponent(props: Props) { - const { loginWithUsername } = props - const { t } = useTranslation() - - const requireUsername = loginWithUsername && loginWithUsername.requireUsername - const showUsernameField = Boolean(loginWithUsername) - - if (showUsernameField) { - return ( - - ) - } - - return null -} - -type RenderEmailAndUsernameFieldsProps = { - className?: string - loginWithUsername?: false | LoginWithUsernameOptions - operation?: 'create' | 'update' - permissions?: { - [fieldName: string]: FieldPermissions - } - readOnly: boolean -} -export function RenderEmailAndUsernameFields(props: RenderEmailAndUsernameFieldsProps) { - const { className, loginWithUsername, operation, permissions, readOnly } = props - - return ( - , - }, - }, - }, - localized: false, - }, - { - name: 'username', - type: 'text', - admin: { - components: { - Field: { - type: 'client', - Component: null, - RenderedComponent: , - }, - }, - }, - localized: false, - }, - ]} - forceRender - operation={operation} - path="" - permissions={permissions} - readOnly={readOnly} - schemaPath="" - /> - ) -} diff --git a/packages/next/src/elements/Logo/index.tsx b/packages/next/src/elements/Logo/index.tsx index 99943074e..5412b91d7 100644 --- a/packages/next/src/elements/Logo/index.tsx +++ b/packages/next/src/elements/Logo/index.tsx @@ -1,6 +1,7 @@ import type { ServerProps } from 'payload' -import { getCreateMappedComponent, PayloadLogo, RenderComponent } from '@payloadcms/ui/shared' +import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent' +import { PayloadLogo } from '@payloadcms/ui/shared' import React from 'react' export const Logo: React.FC = (props) => { @@ -16,20 +17,20 @@ export const Logo: React.FC = (props) => { } = {}, } = payload.config - const createMappedComponent = getCreateMappedComponent({ - importMap: payload.importMap, - serverProps: { - i18n, - locale, - params, - payload, - permissions, - searchParams, - user, - }, - }) - - const mappedCustomLogo = createMappedComponent(CustomLogo, undefined, PayloadLogo, 'CustomLogo') - - return + return ( + + ) } diff --git a/packages/next/src/elements/Nav/index.client.tsx b/packages/next/src/elements/Nav/index.client.tsx index 3a19a0cd3..9187cf430 100644 --- a/packages/next/src/elements/Nav/index.client.tsx +++ b/packages/next/src/elements/Nav/index.client.tsx @@ -1,32 +1,23 @@ 'use client' -import type { EntityToGroup } from '@payloadcms/ui/shared' +import type { groupNavItems } from '@payloadcms/ui/shared' import { getTranslation } from '@payloadcms/translations' -import { - NavGroup, - useAuth, - useConfig, - useEntityVisibility, - useNav, - useTranslation, -} from '@payloadcms/ui' -import { EntityType, formatAdminURL, groupNavItems } from '@payloadcms/ui/shared' +import { NavGroup, useConfig, useNav, useTranslation } from '@payloadcms/ui' +import { EntityType, formatAdminURL } from '@payloadcms/ui/shared' import LinkWithDefault from 'next/link.js' import { usePathname } from 'next/navigation.js' import React, { Fragment } from 'react' const baseClass = 'nav' -export const DefaultNavClient: React.FC = () => { - const { permissions } = useAuth() - const { isEntityVisible } = useEntityVisibility() +export const DefaultNavClient: React.FC<{ + groups: ReturnType +}> = ({ groups }) => { const pathname = usePathname() const { config: { - collections, - globals, routes: { admin: adminRoute }, }, } = useConfig() @@ -34,53 +25,23 @@ export const DefaultNavClient: React.FC = () => { const { i18n } = useTranslation() const { navOpen } = useNav() - const groups = groupNavItems( - [ - ...collections - .filter(({ slug }) => isEntityVisible({ collectionSlug: slug })) - .map((collection) => { - const entityToGroup: EntityToGroup = { - type: EntityType.collection, - entity: collection, - } - - return entityToGroup - }), - ...globals - .filter(({ slug }) => isEntityVisible({ globalSlug: slug })) - .map((global) => { - const entityToGroup: EntityToGroup = { - type: EntityType.global, - entity: global, - } - - return entityToGroup - }), - ], - permissions, - i18n, - ) - return ( {groups.map(({ entities, label }, key) => { return ( - {entities.map(({ type, entity }, i) => { - let entityLabel: string + {entities.map(({ slug, type, label }, i) => { let href: string let id: string if (type === EntityType.collection) { - href = formatAdminURL({ adminRoute, path: `/collections/${entity.slug}` }) - entityLabel = getTranslation(entity.labels.plural, i18n) - id = `nav-${entity.slug}` + href = formatAdminURL({ adminRoute, path: `/collections/${slug}` }) + id = `nav-${slug}` } if (type === EntityType.global) { - href = formatAdminURL({ adminRoute, path: `/globals/${entity.slug}` }) - entityLabel = getTranslation(entity.label, i18n) - id = `nav-global-${entity.slug}` + href = formatAdminURL({ adminRoute, path: `/globals/${slug}` }) + id = `nav-global-${slug}` } const Link = (LinkWithDefault.default || @@ -102,7 +63,7 @@ export const DefaultNavClient: React.FC = () => { tabIndex={!navOpen ? -1 : undefined} > {activeCollection &&
} - {entityLabel} + {getTranslation(label, i18n)} ) })} diff --git a/packages/next/src/elements/Nav/index.tsx b/packages/next/src/elements/Nav/index.tsx index 11611ed62..b41eff558 100644 --- a/packages/next/src/elements/Nav/index.tsx +++ b/packages/next/src/elements/Nav/index.tsx @@ -1,7 +1,9 @@ +import type { EntityToGroup } from '@payloadcms/ui/shared' import type { ServerProps } from 'payload' import { Logout } from '@payloadcms/ui' -import { getCreateMappedComponent, RenderComponent } from '@payloadcms/ui/shared' +import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent' +import { EntityType, groupNavItems } from '@payloadcms/ui/shared' import React from 'react' import './index.scss' @@ -15,7 +17,7 @@ import { DefaultNavClient } from './index.client.js' export type NavProps = ServerProps export const DefaultNav: React.FC = (props) => { - const { i18n, locale, params, payload, permissions, searchParams, user } = props + const { i18n, locale, params, payload, permissions, searchParams, user, visibleEntities } = props if (!payload?.config) { return null @@ -23,44 +25,82 @@ export const DefaultNav: React.FC = (props) => { const { admin: { - components: { afterNavLinks, beforeNavLinks }, + components: { afterNavLinks, beforeNavLinks, logout }, }, + collections, + globals, } = payload.config - const createMappedComponent = getCreateMappedComponent({ - importMap: payload.importMap, - serverProps: { - i18n, - locale, - params, - payload, - permissions, - searchParams, - user, - }, - }) - - const mappedBeforeNavLinks = createMappedComponent( - beforeNavLinks, - undefined, - undefined, - 'beforeNavLinks', - ) - const mappedAfterNavLinks = createMappedComponent( - afterNavLinks, - undefined, - undefined, - 'afterNavLinks', + const groups = groupNavItems( + [ + ...collections + .filter(({ slug }) => visibleEntities.collections.includes(slug)) + .map( + (collection) => + ({ + type: EntityType.collection, + entity: collection, + }) satisfies EntityToGroup, + ), + ...globals + .filter(({ slug }) => visibleEntities.globals.includes(slug)) + .map( + (global) => + ({ + type: EntityType.global, + entity: global, + }) satisfies EntityToGroup, + ), + ], + permissions, + i18n, ) return (
diff --git a/packages/next/src/exports/layouts.ts b/packages/next/src/exports/layouts.ts index d3a56a750..a0d56aa30 100644 --- a/packages/next/src/exports/layouts.ts +++ b/packages/next/src/exports/layouts.ts @@ -1 +1,2 @@ export { metadata, RootLayout } from '../layouts/Root/index.js' +export { handleServerFunctions } from '../utilities/handleServerFunctions.js' diff --git a/packages/next/src/exports/utilities.ts b/packages/next/src/exports/utilities.ts index 041bc67e0..a51d6d597 100644 --- a/packages/next/src/exports/utilities.ts +++ b/packages/next/src/exports/utilities.ts @@ -1,3 +1,4 @@ +// NOTICE: Server-only utilities, do not import anything client-side here. export { addDataAndFileToRequest } from '../utilities/addDataAndFileToRequest.js' export { addLocalesToRequestFromData, sanitizeLocales } from '../utilities/addLocalesToRequest.js' export { createPayloadRequest } from '../utilities/createPayloadRequest.js' diff --git a/packages/next/src/exports/views.ts b/packages/next/src/exports/views.ts index 4d777b32e..fe3309ca4 100644 --- a/packages/next/src/exports/views.ts +++ b/packages/next/src/exports/views.ts @@ -1,4 +1,2 @@ -export { DefaultEditView as EditView } from '../views/Edit/Default/index.js' -export { DefaultListView as ListView } from '../views/List/Default/index.js' export { NotFoundPage } from '../views/NotFound/index.js' export { generatePageMetadata, type GenerateViewMetadata, RootPage } from '../views/Root/index.js' diff --git a/packages/next/src/fetchAPI-multipart/processMultipart.ts b/packages/next/src/fetchAPI-multipart/processMultipart.ts index 64f2dbba0..733e10ff7 100644 --- a/packages/next/src/fetchAPI-multipart/processMultipart.ts +++ b/packages/next/src/fetchAPI-multipart/processMultipart.ts @@ -22,6 +22,7 @@ type ProcessMultipart = (args: { export const processMultipart: ProcessMultipart = async ({ options, request }) => { let parsingRequest = true + let shouldAbortProccessing = false let fileCount = 0 let filesCompleted = 0 let allFilesHaveResolved: (value?: unknown) => void @@ -42,14 +43,16 @@ export const processMultipart: ProcessMultipart = async ({ options, request }) = headersObject[name] = value }) - function abortAndDestroyFile(file: Readable, err: APIError) { - file.destroy() - parsingRequest = false - failedResolvingFiles(err) - } + const reader = request.body.getReader() const busboy = Busboy({ ...options, headers: headersObject }) + function abortAndDestroyFile(file: Readable, err: APIError) { + file.destroy() + shouldAbortProccessing = true + failedResolvingFiles(err) + } + // Build multipart req.body fields busboy.on('field', (field, val) => { result.fields = buildFields(result.fields, field, val) @@ -136,7 +139,7 @@ export const processMultipart: ProcessMultipart = async ({ options, request }) = mimetype: mime, size, tempFilePath: getFilePath(), - truncated: Boolean('truncated' in file && file.truncated), + truncated: Boolean('truncated' in file && file.truncated) || false, }, options, ), @@ -164,8 +167,6 @@ export const processMultipart: ProcessMultipart = async ({ options, request }) = uploadTimer.set() }) - // TODO: Valid eslint error - this will likely be a floating promise. Evaluate if we need to handle this differently. - busboy.on('finish', async () => { debugLog(options, `Busboy finished parsing request.`) if (options.parseNested) { @@ -190,14 +191,10 @@ export const processMultipart: ProcessMultipart = async ({ options, request }) = 'error', (err = new APIError('Busboy error parsing multipart request', httpStatus.BAD_REQUEST)) => { debugLog(options, `Busboy error`) - parsingRequest = false throw err }, ) - const reader = request.body.getReader() - - // Start parsing request while (parsingRequest) { const { done, value } = await reader.read() @@ -205,7 +202,7 @@ export const processMultipart: ProcessMultipart = async ({ options, request }) = parsingRequest = false } - if (value) { + if (value && !shouldAbortProccessing) { busboy.write(value) } } diff --git a/packages/next/src/layouts/Root/NestProviders.tsx b/packages/next/src/layouts/Root/NestProviders.tsx new file mode 100644 index 000000000..e92106d43 --- /dev/null +++ b/packages/next/src/layouts/Root/NestProviders.tsx @@ -0,0 +1,30 @@ +import type { Config, ImportMap } from 'payload' + +import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent' +import '@payloadcms/ui/scss/app.scss' +import React from 'react' + +type Args = { + readonly children: React.ReactNode + readonly importMap: ImportMap + readonly providers: Config['admin']['components']['providers'] +} + +export function NestProviders({ children, importMap, providers }: Args): React.ReactNode { + return ( + 1 ? ( + + {children} + + ) : ( + children + ), + }} + Component={providers[0]} + importMap={importMap} + /> + ) +} diff --git a/packages/next/src/layouts/Root/index.tsx b/packages/next/src/layouts/Root/index.tsx index e9a4f45be..dcb15503c 100644 --- a/packages/next/src/layouts/Root/index.tsx +++ b/packages/next/src/layouts/Root/index.tsx @@ -1,20 +1,19 @@ import type { AcceptedLanguages } from '@payloadcms/translations' -import type { CustomVersionParser, ImportMap, SanitizedConfig } from 'payload' +import type { CustomVersionParser, ImportMap, SanitizedConfig, ServerFunctionClient } from 'payload' import { rtlLanguages } from '@payloadcms/translations' import { RootProvider } from '@payloadcms/ui' import '@payloadcms/ui/scss/app.scss' -import { createClientConfig } from '@payloadcms/ui/utilities/createClientConfig' import { headers as getHeaders, cookies as nextCookies } from 'next/headers.js' import { checkDependencies, parseCookies } from 'payload' import React from 'react' +import { getClientConfig } from '../../utilities/getClientConfig.js' import { getPayloadHMR } from '../../utilities/getPayloadHMR.js' import { getRequestLanguage } from '../../utilities/getRequestLanguage.js' import { getRequestTheme } from '../../utilities/getRequestTheme.js' import { initReq } from '../../utilities/initReq.js' -import { DefaultEditView } from '../../views/Edit/Default/index.js' -import { DefaultListView } from '../../views/List/Default/index.js' +import { NestProviders } from './NestProviders.js' export const metadata = { description: 'Generated by Next.js', @@ -41,11 +40,12 @@ let checkedDependencies = false export const RootLayout = async ({ children, config: configPromise, - importMap, + serverFunction, }: { readonly children: React.ReactNode readonly config: Promise readonly importMap: ImportMap + readonly serverFunction: ServerFunctionClient }) => { if ( process.env.NODE_ENV !== 'production' && @@ -103,16 +103,6 @@ export const RootLayout = async ({ const { i18n, permissions, req, user } = await initReq(config) - const { clientConfig, render } = await createClientConfig({ - children, - config, - DefaultEditView, - DefaultListView, - i18n, - importMap, - payload, - }) - const dir = (rtlLanguages as unknown as AcceptedLanguages[]).includes(languageCode) ? 'RTL' : 'LTR' @@ -174,23 +164,39 @@ export const RootLayout = async ({ const isNavOpen = navPreferences?.value?.open ?? true + const clientConfig = await getClientConfig({ + config, + i18n, + }) + return ( - {render} + {Array.isArray(config.admin?.components?.providers) && + config.admin?.components?.providers.length > 0 ? ( + + {children} + + ) : ( + children + )}
diff --git a/packages/next/src/routes/rest/buildFormState.ts b/packages/next/src/routes/rest/buildFormState.ts deleted file mode 100644 index e997a276c..000000000 --- a/packages/next/src/routes/rest/buildFormState.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { PayloadRequest } from 'payload' - -import { buildFormState as buildFormStateFn } from '@payloadcms/ui/utilities/buildFormState' -import httpStatus from 'http-status' - -import { headersWithCors } from '../../utilities/headersWithCors.js' -import { routeError } from './routeError.js' - -export const buildFormState = async ({ req }: { req: PayloadRequest }) => { - const headers = headersWithCors({ - headers: new Headers(), - req, - }) - - try { - const result = await buildFormStateFn({ req }) - - return Response.json(result, { - headers, - status: httpStatus.OK, - }) - } catch (err) { - req.payload.logger.error({ err, msg: `There was an error building form state` }) - - if (err.message === 'Could not find field schema for given path') { - return Response.json( - { - message: err.message, - }, - { - headers, - status: httpStatus.BAD_REQUEST, - }, - ) - } - - if (err.message === 'Unauthorized') { - return Response.json(null, { - headers, - status: httpStatus.UNAUTHORIZED, - }) - } - - return routeError({ - config: req.payload.config, - err, - req, - }) - } -} diff --git a/packages/next/src/routes/rest/index.ts b/packages/next/src/routes/rest/index.ts index 58a707385..4858a9a52 100644 --- a/packages/next/src/routes/rest/index.ts +++ b/packages/next/src/routes/rest/index.ts @@ -26,7 +26,6 @@ import { registerFirstUser } from './auth/registerFirstUser.js' import { resetPassword } from './auth/resetPassword.js' import { unlock } from './auth/unlock.js' import { verifyEmail } from './auth/verifyEmail.js' -import { buildFormState } from './buildFormState.js' import { endpointsAreDisabled } from './checkEndpoints.js' import { count } from './collections/count.js' import { create } from './collections/create.js' @@ -110,9 +109,6 @@ const endpoints = { access, og: generateOGImage, }, - POST: { - 'form-state': buildFormState, - }, }, } @@ -575,10 +571,6 @@ export const POST = res = new Response('Route Not Found', { status: 404 }) } } - } else if (slug.length === 1 && slug1 in endpoints.root.POST) { - await addDataAndFileToRequest(req) - addLocalesToRequestFromData(req) - res = await endpoints.root.POST[slug1]({ req }) } if (res instanceof Response) { diff --git a/packages/next/src/routes/rest/og/image.tsx b/packages/next/src/routes/rest/og/image.tsx index 855f3aed2..4bc2b7a69 100644 --- a/packages/next/src/routes/rest/og/image.tsx +++ b/packages/next/src/routes/rest/og/image.tsx @@ -1,15 +1,25 @@ -import type { MappedComponent } from 'payload' +import type { ImportMap, PayloadComponent } from 'payload' -import { RenderComponent } from '@payloadcms/ui/shared' +import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent' import React from 'react' export const OGImage: React.FC<{ description?: string + Fallback: React.ComponentType fontFamily?: string - Icon: MappedComponent + Icon: PayloadComponent + importMap: ImportMap leader?: string title?: string -}> = ({ description, fontFamily = 'Arial, sans-serif', Icon, leader, title }) => { +}> = ({ + description, + Fallback, + fontFamily = 'Arial, sans-serif', + Icon, + importMap, + leader, + title, +}) => { return (
-
diff --git a/packages/next/src/routes/rest/og/index.tsx b/packages/next/src/routes/rest/og/index.tsx index 7f406ee54..ab6bf7fb3 100644 --- a/packages/next/src/routes/rest/og/index.tsx +++ b/packages/next/src/routes/rest/og/index.tsx @@ -1,6 +1,6 @@ import type { PayloadRequest } from 'payload' -import { getCreateMappedComponent, PayloadIcon } from '@payloadcms/ui/shared' +import { PayloadIcon } from '@payloadcms/ui/shared' import fs from 'fs/promises' import { ImageResponse } from 'next/og.js' import { NextResponse } from 'next/server.js' @@ -33,18 +33,6 @@ export const generateOGImage = async ({ req }: { req: PayloadRequest }) => { const leader = hasLeader ? searchParams.get('leader')?.slice(0, 100).replace('-', ' ') : '' const description = searchParams.has('description') ? searchParams.get('description') : '' - const createMappedComponent = getCreateMappedComponent({ - importMap: req.payload.importMap, - serverProps: {}, - }) - - const mappedIcon = createMappedComponent( - config.admin?.components?.graphics?.Icon, - undefined, - PayloadIcon, - 'config.admin.components.graphics.Icon', - ) - let fontData try { @@ -62,8 +50,10 @@ export const generateOGImage = async ({ req }: { req: PayloadRequest }) => { ( diff --git a/packages/next/src/routes/rest/routeError.ts b/packages/next/src/routes/rest/routeError.ts index c9dd6c61d..ac5a5ad9b 100644 --- a/packages/next/src/routes/rest/routeError.ts +++ b/packages/next/src/routes/rest/routeError.ts @@ -1,73 +1,12 @@ import type { Collection, ErrorResult, PayloadRequest, SanitizedConfig } from 'payload' import httpStatus from 'http-status' -import { APIError, APIErrorName, ValidationErrorName } from 'payload' +import { APIError, formatErrors } from 'payload' import { getPayloadHMR } from '../../utilities/getPayloadHMR.js' import { headersWithCors } from '../../utilities/headersWithCors.js' import { mergeHeaders } from '../../utilities/mergeHeaders.js' -const formatErrors = (incoming: { [key: string]: unknown } | APIError): ErrorResult => { - if (incoming) { - // Cannot use `instanceof` to check error type: https://github.com/microsoft/TypeScript/issues/13965 - // Instead, get the prototype of the incoming error and check its constructor name - const proto = Object.getPrototypeOf(incoming) - - // Payload 'ValidationError' and 'APIError' - if ( - (proto.constructor.name === ValidationErrorName || proto.constructor.name === APIErrorName) && - incoming.data - ) { - return { - errors: [ - { - name: incoming.name, - data: incoming.data, - message: incoming.message, - }, - ], - } - } - - // Mongoose 'ValidationError': https://mongoosejs.com/docs/api/error.html#Error.ValidationError - if (proto.constructor.name === ValidationErrorName && 'errors' in incoming && incoming.errors) { - return { - errors: Object.keys(incoming.errors).reduce((acc, key) => { - acc.push({ - field: incoming.errors[key].path, - message: incoming.errors[key].message, - }) - return acc - }, []), - } - } - - if (Array.isArray(incoming.message)) { - return { - errors: incoming.message, - } - } - - if (incoming.name) { - return { - errors: [ - { - message: incoming.message, - }, - ], - } - } - } - - return { - errors: [ - { - message: 'An unknown error occurred.', - }, - ], - } -} - export const routeError = async ({ collection, config: configArg, diff --git a/packages/next/src/templates/Default/index.tsx b/packages/next/src/templates/Default/index.tsx index 26936a723..ee974ab8b 100644 --- a/packages/next/src/templates/Default/index.tsx +++ b/packages/next/src/templates/Default/index.tsx @@ -1,7 +1,13 @@ -import type { MappedComponent, ServerProps, VisibleEntities } from 'payload' +import type { CustomComponent, ServerProps, VisibleEntities } from 'payload' -import { AppHeader, BulkUploadProvider, EntityVisibilityProvider, NavToggler } from '@payloadcms/ui' -import { getCreateMappedComponent, RenderComponent } from '@payloadcms/ui/shared' +import { + ActionsProvider, + AppHeader, + BulkUploadProvider, + EntityVisibilityProvider, + NavToggler, +} from '@payloadcms/ui' +import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent' import React from 'react' import { DefaultNav } from '../../elements/Nav/index.js' @@ -14,6 +20,7 @@ const baseClass = 'template-default' export type DefaultTemplateProps = { children?: React.ReactNode className?: string + viewActions?: CustomComponent[] visibleEntities: VisibleEntities } & ServerProps @@ -27,10 +34,13 @@ export const DefaultTemplate: React.FC = ({ permissions, searchParams, user, + viewActions, visibleEntities, }) => { const { admin: { + avatar, + components, components: { header: CustomHeader, Nav: CustomNav } = { header: undefined, Nav: undefined, @@ -38,54 +48,98 @@ export const DefaultTemplate: React.FC = ({ } = {}, } = payload.config || {} - const createMappedComponent = getCreateMappedComponent({ - importMap: payload.importMap, - serverProps: { - i18n, - locale, - params, - payload, - permissions, - searchParams, - user, - }, - }) + const { Actions } = React.useMemo<{ + Actions: Record + }>(() => { + return { + Actions: viewActions + ? viewActions.reduce((acc, action) => { + if (action) { + if (typeof action === 'object') { + acc[action.path] = ( + + ) + } else { + acc[action] = ( + + ) + } + } - const MappedDefaultNav: MappedComponent = createMappedComponent( - CustomNav, - undefined, - DefaultNav, - 'CustomNav', - ) - - const MappedCustomHeader = createMappedComponent( - CustomHeader, - undefined, - undefined, - 'CustomHeader', - ) + return acc + }, {}) + : undefined, + } + }, [viewActions, payload]) return ( - -
-