diff --git a/packages/next/src/layouts/Root/index.tsx b/packages/next/src/layouts/Root/index.tsx index 059920498..d9145cc16 100644 --- a/packages/next/src/layouts/Root/index.tsx +++ b/packages/next/src/layouts/Root/index.tsx @@ -12,6 +12,7 @@ import { createClientConfig } from 'payload/config' import React from 'react' import 'react-toastify/dist/ReactToastify.css' +import { getPayloadHMR } from '../../utilities/getPayloadHMR.js' import { getRequestLanguage } from '../../utilities/getRequestLanguage.js' import { DefaultEditView } from '../../views/Edit/Default/index.js' import { DefaultListView } from '../../views/List/Default/index.js' @@ -39,6 +40,7 @@ export const RootLayout = async ({ headers, }) + const payload = await getPayloadHMR({ config }) const i18n = await initI18n({ config: config.i18n, context: 'client', language: languageCode }) const clientConfig = await createClientConfig({ config, t: i18n.t }) @@ -76,6 +78,7 @@ export const RootLayout = async ({ children, config, i18n, + payload, }) return ( diff --git a/packages/next/src/views/CreateFirstUser/index.tsx b/packages/next/src/views/CreateFirstUser/index.tsx index 2c8e55716..bb6917ecc 100644 --- a/packages/next/src/views/CreateFirstUser/index.tsx +++ b/packages/next/src/views/CreateFirstUser/index.tsx @@ -1,9 +1,10 @@ -import type { Field } from 'payload/types' +import type { Field, WithServerSideProps as WithServerSidePropsType } from 'payload/types' import type { AdminViewProps } from 'payload/types' import { Form } from '@payloadcms/ui/forms/Form' import { FormSubmit } from '@payloadcms/ui/forms/Submit' import { buildStateFromSchema } from '@payloadcms/ui/forms/buildStateFromSchema' +import { WithServerSideProps as WithServerSidePropsGeneric } from '@payloadcms/ui/providers/ComponentMap' import { mapFields } from '@payloadcms/ui/utilities/buildComponentMap' import React from 'react' @@ -17,6 +18,7 @@ export const CreateFirstUserView: React.FC = async ({ initPageRe req, req: { i18n, + payload, payload: { config, config: { @@ -49,7 +51,12 @@ export const CreateFirstUserView: React.FC = async ({ initPageRe }, ] + const WithServerSideProps: WithServerSidePropsType = ({ Component, ...rest }) => { + return + } + const createFirstUserFieldMap = mapFields({ + WithServerSideProps, config, fieldSchema: fields, i18n, diff --git a/packages/payload/src/admin/RichText.ts b/packages/payload/src/admin/RichText.ts index 36d4eb59e..6f67ae8b9 100644 --- a/packages/payload/src/admin/RichText.ts +++ b/packages/payload/src/admin/RichText.ts @@ -4,6 +4,7 @@ import type { JSONSchema4 } from 'json-schema' import type { SanitizedConfig } from '../config/types.js' import type { Field, RichTextField, Validate } from '../fields/config/types.js' import type { PayloadRequest, RequestContext } from '../types/index.js' +import type { WithServerSideProps } from './elements/WithServerSideProps.js' export type RichTextFieldProps< Value extends object, @@ -28,6 +29,7 @@ type RichTextAdapterBase< siblingDoc: Record }) => Promise | null generateComponentMap: (args: { + WithServerSideProps: WithServerSideProps config: SanitizedConfig i18n: I18n schemaPath: string diff --git a/packages/payload/src/admin/elements/PreviewButton.ts b/packages/payload/src/admin/elements/PreviewButton.ts index 3763db684..dad525625 100644 --- a/packages/payload/src/admin/elements/PreviewButton.ts +++ b/packages/payload/src/admin/elements/PreviewButton.ts @@ -1 +1,3 @@ -export type CustomPreviewButton = React.ComponentType +import type { CustomComponent } from '../../config/types.js' + +export type CustomPreviewButton = CustomComponent diff --git a/packages/payload/src/admin/elements/PublishButton.ts b/packages/payload/src/admin/elements/PublishButton.ts index 40fb78c03..8fabd59c2 100644 --- a/packages/payload/src/admin/elements/PublishButton.ts +++ b/packages/payload/src/admin/elements/PublishButton.ts @@ -1 +1,3 @@ -export type CustomPublishButton = React.ComponentType +import type { CustomComponent } from '../../config/types.js' + +export type CustomPublishButton = CustomComponent diff --git a/packages/payload/src/admin/elements/SaveButton.ts b/packages/payload/src/admin/elements/SaveButton.ts index 81b232cff..917775cc6 100644 --- a/packages/payload/src/admin/elements/SaveButton.ts +++ b/packages/payload/src/admin/elements/SaveButton.ts @@ -1 +1,3 @@ -export type CustomSaveButton = React.ComponentType +import type { CustomComponent } from '../../config/types.js' + +export type CustomSaveButton = CustomComponent diff --git a/packages/payload/src/admin/elements/SaveDraftButton.ts b/packages/payload/src/admin/elements/SaveDraftButton.ts index 6e7c04b99..ff2f410ae 100644 --- a/packages/payload/src/admin/elements/SaveDraftButton.ts +++ b/packages/payload/src/admin/elements/SaveDraftButton.ts @@ -1 +1,3 @@ -export type CustomSaveDraftButton = React.ComponentType +import type { CustomComponent } from '../../config/types.js' + +export type CustomSaveDraftButton = CustomComponent diff --git a/packages/payload/src/admin/elements/WithServerSideProps.ts b/packages/payload/src/admin/elements/WithServerSideProps.ts new file mode 100644 index 000000000..2cd3149ed --- /dev/null +++ b/packages/payload/src/admin/elements/WithServerSideProps.ts @@ -0,0 +1,4 @@ +export type WithServerSideProps = (args: { + [key: string]: any + Component: React.ComponentType +}) => React.ReactNode diff --git a/packages/payload/src/admin/forms/FieldDescription.ts b/packages/payload/src/admin/forms/FieldDescription.ts index cd2c2fd9f..7974559df 100644 --- a/packages/payload/src/admin/forms/FieldDescription.ts +++ b/packages/payload/src/admin/forms/FieldDescription.ts @@ -1,10 +1,11 @@ import type React from 'react' -import type { LabelFunction } from '../../config/types.js' +import type { CustomComponent, LabelFunction } from '../../config/types.js' +import type { Payload } from '../../index.js' export type DescriptionFunction = LabelFunction -export type DescriptionComponent = React.ComponentType +export type DescriptionComponent = CustomComponent export type Description = | DescriptionComponent @@ -17,4 +18,5 @@ export type FieldDescriptionProps = { className?: string description?: Record | string marginPlacement?: 'bottom' | 'top' + payload?: Payload } diff --git a/packages/payload/src/admin/forms/RowLabel.ts b/packages/payload/src/admin/forms/RowLabel.ts index 6a7dc0533..d75d7b0a7 100644 --- a/packages/payload/src/admin/forms/RowLabel.ts +++ b/packages/payload/src/admin/forms/RowLabel.ts @@ -1,3 +1,5 @@ -export type RowLabelComponent = React.ComponentType +import type { CustomComponent } from '../../config/types.js' + +export type RowLabelComponent = CustomComponent export type RowLabel = Record | RowLabelComponent | string diff --git a/packages/payload/src/admin/types.ts b/packages/payload/src/admin/types.ts index cf85d435a..efd4ddb7b 100644 --- a/packages/payload/src/admin/types.ts +++ b/packages/payload/src/admin/types.ts @@ -13,6 +13,7 @@ export type { DocumentTabConfig, DocumentTabProps, } from './elements/Tab.js' +export type { WithServerSideProps } from './elements/WithServerSideProps.js' export type { ErrorProps } from './forms/Error.js' export type { Description, diff --git a/packages/payload/src/collections/config/types.ts b/packages/payload/src/collections/config/types.ts index 0decc884e..1d3344bb1 100644 --- a/packages/payload/src/collections/config/types.ts +++ b/packages/payload/src/collections/config/types.ts @@ -10,6 +10,7 @@ import type { import type { Auth, ClientUser, IncomingAuthType } from '../../auth/types.js' import type { Access, + CustomComponent, EditConfig, Endpoint, EntityDescription, @@ -201,10 +202,10 @@ export type CollectionAdminOptions = { * Custom admin components */ components?: { - AfterList?: React.ComponentType[] - AfterListTable?: React.ComponentType[] - BeforeList?: React.ComponentType[] - BeforeListTable?: React.ComponentType[] + AfterList?: CustomComponent[] + AfterListTable?: CustomComponent[] + BeforeList?: CustomComponent[] + BeforeListTable?: CustomComponent[] /** * Components within the edit view */ @@ -239,7 +240,7 @@ export type CollectionAdminOptions = { List?: | { Component?: React.ComponentType - actions?: React.ComponentType[] + actions?: CustomComponent[] } | React.ComponentType } diff --git a/packages/payload/src/config/types.ts b/packages/payload/src/config/types.ts index 7dbc69c05..4799b0632 100644 --- a/packages/payload/src/config/types.ts +++ b/packages/payload/src/config/types.ts @@ -279,7 +279,7 @@ export type EditViewConfig = path: string } | { - actions?: React.ComponentType[] + actions?: CustomComponent[] } /** @@ -291,6 +291,14 @@ export type EditViewConfig = */ export type EditView = EditViewComponent | EditViewConfig +export type ServerProps = { + payload: Payload +} + +export const serverProps: (keyof ServerProps)[] = ['payload'] + +export type CustomComponent = React.ComponentType + export type Locale = { /** * value of supported locale @@ -420,46 +428,46 @@ export type Config = { /** * Replace the navigation with a custom component */ - Nav?: React.ComponentType + Nav?: CustomComponent /** * Add custom components to the top right of the Admin Panel */ - actions?: React.ComponentType[] + actions?: CustomComponent[] /** * Add custom components after the collection overview */ - afterDashboard?: React.ComponentType[] + afterDashboard?: CustomComponent[] /** * Add custom components after the email/password field */ - afterLogin?: React.ComponentType[] + afterLogin?: CustomComponent[] /** * Add custom components after the navigation links */ - afterNavLinks?: React.ComponentType[] + afterNavLinks?: CustomComponent[] /** * Add custom components before the collection overview */ - beforeDashboard?: React.ComponentType[] + beforeDashboard?: CustomComponent[] /** * Add custom components before the email/password field */ - beforeLogin?: React.ComponentType[] + beforeLogin?: CustomComponent[] /** * Add custom components before the navigation links */ - beforeNavLinks?: React.ComponentType[] + beforeNavLinks?: CustomComponent[] /** Replace graphical components */ graphics?: { /** Replace the icon in the navigation */ - Icon?: React.ComponentType + Icon?: CustomComponent /** Replace the logo on the login page */ - Logo?: React.ComponentType + Logo?: CustomComponent } /** Replace logout related components */ logout?: { /** Replace the logout button */ - Button?: React.ComponentType + Button?: CustomComponent } /** * Wrap the admin dashboard in custom context providers @@ -716,7 +724,7 @@ export type EditConfig = ) | EditViewComponent -export type EntityDescriptionComponent = React.ComponentType +export type EntityDescriptionComponent = CustomComponent export type EntityDescriptionFunction = () => string diff --git a/packages/payload/src/exports/utilities.ts b/packages/payload/src/exports/utilities.ts index 1e50cea38..54e426ce1 100644 --- a/packages/payload/src/exports/utilities.ts +++ b/packages/payload/src/exports/utilities.ts @@ -34,7 +34,12 @@ export { isEntityHidden } from '../utilities/isEntityHidden.js' export { isNumber } from '../utilities/isNumber.js' export { isPlainObject } from '../utilities/isPlainObject.js' -export { isPlainFunction, isReactComponent } from '../utilities/isReactComponent.js' +export { + isPlainFunction, + isReactClientComponent, + isReactComponent, + isReactServerComponent, +} from '../utilities/isReactComponent.js' export { isValidID } from '../utilities/isValidID.js' export { default as isolateObjectProperty } from '../utilities/isolateObjectProperty.js' diff --git a/packages/payload/src/fields/config/types.ts b/packages/payload/src/fields/config/types.ts index ad656e66a..946d0f4a8 100644 --- a/packages/payload/src/fields/config/types.ts +++ b/packages/payload/src/fields/config/types.ts @@ -17,7 +17,7 @@ import type { } from '../../admin/types.js' import type { User } from '../../auth/index.js' import type { SanitizedCollectionConfig, TypeWithID } from '../../collections/config/types.js' -import type { LabelFunction } from '../../config/types.js' +import type { CustomComponent, LabelFunction } from '../../config/types.js' import type { DBIdentifierName } from '../../database/types.js' import type { SanitizedGlobalConfig } from '../../globals/config/types.js' import type { Operation, PayloadRequest, RequestContext, Where } from '../../types/index.js' @@ -115,8 +115,8 @@ export type FilterOptions = type Admin = { className?: string components?: { - Cell?: React.ComponentType - Field?: React.ComponentType + Cell?: CustomComponent + Field?: CustomComponent Filter?: React.ComponentType } /** @@ -208,10 +208,10 @@ export type NumberField = FieldBase & { /** Set this property to a string that will be used for browser autocomplete. */ autoComplete?: string components?: { - Error?: React.ComponentType - Label?: React.ComponentType - afterInput?: React.ComponentType[] - beforeInput?: React.ComponentType[] + Error?: CustomComponent + Label?: CustomComponent + afterInput?: CustomComponent[] + beforeInput?: CustomComponent[] } /** Set this property to define a placeholder string for the field. */ placeholder?: Record | string @@ -246,10 +246,10 @@ export type TextField = FieldBase & { admin?: Admin & { autoComplete?: string components?: { - Error?: React.ComponentType - Label?: React.ComponentType - afterInput?: React.ComponentType[] - beforeInput?: React.ComponentType[] + Error?: CustomComponent + Label?: CustomComponent + afterInput?: CustomComponent[] + beforeInput?: CustomComponent[] } placeholder?: Record | string rtl?: boolean @@ -280,10 +280,10 @@ export type EmailField = FieldBase & { admin?: Admin & { autoComplete?: string components?: { - Error?: React.ComponentType - Label?: React.ComponentType - afterInput?: React.ComponentType[] - beforeInput?: React.ComponentType[] + Error?: CustomComponent + Label?: CustomComponent + afterInput?: CustomComponent[] + beforeInput?: CustomComponent[] } placeholder?: Record | string } @@ -293,10 +293,10 @@ export type EmailField = FieldBase & { export type TextareaField = FieldBase & { admin?: Admin & { components?: { - Error?: React.ComponentType - Label?: React.ComponentType - afterInput?: React.ComponentType[] - beforeInput?: React.ComponentType[] + Error?: CustomComponent + Label?: CustomComponent + afterInput?: CustomComponent[] + beforeInput?: CustomComponent[] } placeholder?: Record | string rows?: number @@ -310,10 +310,10 @@ export type TextareaField = FieldBase & { export type CheckboxField = FieldBase & { admin?: Admin & { components?: { - Error?: React.ComponentType - Label?: React.ComponentType - afterInput?: React.ComponentType[] - beforeInput?: React.ComponentType[] + Error?: CustomComponent + Label?: CustomComponent + afterInput?: CustomComponent[] + beforeInput?: CustomComponent[] } } type: 'checkbox' @@ -322,10 +322,10 @@ export type CheckboxField = FieldBase & { export type DateField = FieldBase & { admin?: Admin & { components?: { - Error?: React.ComponentType - Label?: React.ComponentType - afterInput?: React.ComponentType[] - beforeInput?: React.ComponentType[] + Error?: CustomComponent + Label?: CustomComponent + afterInput?: CustomComponent[] + beforeInput?: CustomComponent[] } date?: ConditionalDateProps placeholder?: Record | string @@ -416,8 +416,8 @@ export type TabAsField = Tab & { export type UIField = { admin: { components?: { - Cell?: React.ComponentType - Field: React.ComponentType + Cell?: CustomComponent + Field: CustomComponent Filter?: React.ComponentType } condition?: Condition @@ -435,8 +435,8 @@ export type UIField = { export type UploadField = FieldBase & { admin?: { components?: { - Error?: React.ComponentType - Label?: React.ComponentType + Error?: CustomComponent + Label?: CustomComponent } } filterOptions?: FilterOptions @@ -447,8 +447,8 @@ export type UploadField = FieldBase & { type CodeAdmin = Admin & { components?: { - Error?: React.ComponentType - Label?: React.ComponentType + Error?: CustomComponent + Label?: CustomComponent } editorOptions?: EditorProps['options'] language?: string @@ -463,8 +463,8 @@ export type CodeField = Omit & { type JSONAdmin = Admin & { components?: { - Error?: React.ComponentType - Label?: React.ComponentType + Error?: CustomComponent + Label?: CustomComponent } editorOptions?: EditorProps['options'] } @@ -477,8 +477,8 @@ export type JSONField = Omit & { export type SelectField = FieldBase & { admin?: Admin & { components?: { - Error?: React.ComponentType - Label?: React.ComponentType + Error?: CustomComponent + Label?: CustomComponent } isClearable?: boolean isSortable?: boolean @@ -533,8 +533,8 @@ type SharedRelationshipProperties = FieldBase & { type RelationshipAdmin = Admin & { allowCreate?: boolean components?: { - Error?: React.ComponentType - Label?: React.ComponentType + Error?: CustomComponent + Label?: CustomComponent } isSortable?: boolean } @@ -574,8 +574,8 @@ export type RichTextField< > = FieldBase & { admin?: Admin & { components?: { - Error?: React.ComponentType - Label?: React.ComponentType + Error?: CustomComponent + Label?: CustomComponent } } editor?: RichTextAdapter @@ -618,8 +618,8 @@ export type ArrayField = FieldBase & { export type RadioField = FieldBase & { admin?: Admin & { components?: { - Error?: React.ComponentType - Label?: React.ComponentType + Error?: CustomComponent + Label?: CustomComponent } layout?: 'horizontal' | 'vertical' } diff --git a/packages/payload/src/utilities/isReactComponent.ts b/packages/payload/src/utilities/isReactComponent.ts index ddba16ce6..3910f1c4a 100644 --- a/packages/payload/src/utilities/isReactComponent.ts +++ b/packages/payload/src/utilities/isReactComponent.ts @@ -4,14 +4,23 @@ import { isValidElement } from 'react' import { isPlainObject } from './isPlainObject.js' +export function isReactServerComponent( + component: React.ComponentType | any, +): component is T { + return typeof component === 'function' && isValidElement(component) +} + +export function isReactClientComponent( + component: React.ComponentType | any, +): component is T { + // Do this to test for client components (`use client` directive) bc they import as empty objects + return typeof component === 'object' && !isPlainObject(component) +} + export function isReactComponent( component: React.ComponentType | any, ): component is T { - return ( - (typeof component === 'function' && isValidElement(component)) || - // Do this to test for client components (`use client` directive) bc they import as empty objects - (typeof component === 'object' && !isPlainObject(component)) - ) + return isReactServerComponent(component) || isReactClientComponent(component) } export function isPlainFunction(fn: any): fn is T { diff --git a/packages/richtext-lexical/src/generateComponentMap.tsx b/packages/richtext-lexical/src/generateComponentMap.tsx index e9002b1fd..480c01853 100644 --- a/packages/richtext-lexical/src/generateComponentMap.tsx +++ b/packages/richtext-lexical/src/generateComponentMap.tsx @@ -13,7 +13,7 @@ export const getGenerateComponentMap = (args: { resolvedFeatureMap: ResolvedServerFeatureMap }): RichTextAdapter['generateComponentMap'] => - ({ config, i18n, schemaPath }) => { + ({ WithServerSideProps, config, i18n, schemaPath }) => { const validRelationships = config.collections.map((c) => c.slug) || [] const componentMap = new Map() @@ -50,7 +50,8 @@ export const getGenerateComponentMap = if (Component) { componentMap.set( `feature.${featureKey}.components.${componentKey}`, - - ({ config, i18n }) => { + ({ WithServerSideProps, config, i18n }) => { const componentMap = new Map() const validRelationships = config.collections.map((c) => c.slug) || [] @@ -73,6 +73,7 @@ export const getGenerateComponentMap = }) const mappedFields = mapFields({ + WithServerSideProps, config, fieldSchema: linkFields, i18n, @@ -104,6 +105,7 @@ export const getGenerateComponentMap = }) const mappedFields = mapFields({ + WithServerSideProps, config, fieldSchema: uploadFields, i18n, diff --git a/packages/ui/src/elements/withMergedProps/index.tsx b/packages/ui/src/elements/withMergedProps/index.tsx index e84ab4b72..03ebd71d3 100644 --- a/packages/ui/src/elements/withMergedProps/index.tsx +++ b/packages/ui/src/elements/withMergedProps/index.tsx @@ -1,3 +1,4 @@ +import { serverProps } from 'payload/config' import { deepMerge } from 'payload/utilities' import React from 'react' @@ -21,14 +22,23 @@ import React from 'react' */ export function withMergedProps({ Component, + sanitizeServerOnlyProps = true, toMergeIntoProps, }: { Component: React.FC + sanitizeServerOnlyProps?: boolean toMergeIntoProps: ToMergeIntoProps }): React.FC { // A wrapper around the args.Component to inject the args.toMergeArgs as props, which are merged with the passed props const MergedPropsComponent: React.FC = (passedProps) => { const mergedProps = deepMerge(passedProps, toMergeIntoProps) + + if (sanitizeServerOnlyProps) { + serverProps.forEach((prop) => { + delete (mergedProps)[prop] + }) + } + return } diff --git a/packages/ui/src/fields/HiddenInput/index.tsx b/packages/ui/src/fields/HiddenInput/index.tsx index 358661a9f..c25aac57a 100644 --- a/packages/ui/src/fields/HiddenInput/index.tsx +++ b/packages/ui/src/fields/HiddenInput/index.tsx @@ -10,7 +10,7 @@ import { withCondition } from '../../forms/withCondition/index.js' export type HiddenInputFieldProps = FormFieldBase & { disableModifyingForm?: false forceUsePathFromProps?: boolean - name: string + name?: string path?: string value?: unknown } diff --git a/packages/ui/src/providers/ComponentMap/buildComponentMap/WithServerSideProps.tsx b/packages/ui/src/providers/ComponentMap/buildComponentMap/WithServerSideProps.tsx new file mode 100644 index 000000000..44d2ab773 --- /dev/null +++ b/packages/ui/src/providers/ComponentMap/buildComponentMap/WithServerSideProps.tsx @@ -0,0 +1,21 @@ +import type { WithServerSideProps as WithServerSidePropsType } from 'payload/types' + +import { isReactServerComponent } from 'payload/utilities' +import React from 'react' + +export const WithServerSideProps: WithServerSidePropsType = ({ Component, payload, ...rest }) => { + if (Component) { + const WithServerSideProps: React.FC = (passedProps) => { + const propsWithPayload = { + ...passedProps, + ...(isReactServerComponent(Component) ? { payload } : {}), + } + + return + } + + return + } + + return null +} diff --git a/packages/ui/src/providers/ComponentMap/buildComponentMap/actions.tsx b/packages/ui/src/providers/ComponentMap/buildComponentMap/actions.tsx index 56c3cc592..7dbb253d8 100644 --- a/packages/ui/src/providers/ComponentMap/buildComponentMap/actions.tsx +++ b/packages/ui/src/providers/ComponentMap/buildComponentMap/actions.tsx @@ -1,14 +1,19 @@ -import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload/types' +import type { + SanitizedCollectionConfig, + SanitizedGlobalConfig, + WithServerSideProps as WithServerSidePropsType, +} from 'payload/types' import React from 'react' import type { ActionMap } from './types.js' export const mapActions = (args: { + WithServerSideProps: WithServerSidePropsType collectionConfig?: SanitizedCollectionConfig globalConfig?: SanitizedGlobalConfig }): ActionMap => { - const { collectionConfig, globalConfig } = args + const { WithServerSideProps, collectionConfig, globalConfig } = args const editViews = (collectionConfig || globalConfig)?.admin?.components?.views?.Edit @@ -28,7 +33,7 @@ export const mapActions = (args: { view.actions.forEach((action) => { const Action = action if (typeof Action === 'function') { - result.Edit[key] = [...(result[key] || []), ] + result.Edit[key] = [...(result[key] || []), ] } }) } @@ -40,7 +45,7 @@ export const mapActions = (args: { const Action = action if (typeof Action === 'function') { // eslint-disable-next-line @typescript-eslint/no-floating-promises - result.List = [...result.List, ] + result.List = [...result.List, ] } }) } diff --git a/packages/ui/src/providers/ComponentMap/buildComponentMap/collections.tsx b/packages/ui/src/providers/ComponentMap/buildComponentMap/collections.tsx index b06caeaa6..ccfa0023f 100644 --- a/packages/ui/src/providers/ComponentMap/buildComponentMap/collections.tsx +++ b/packages/ui/src/providers/ComponentMap/buildComponentMap/collections.tsx @@ -7,6 +7,7 @@ import type { EntityDescriptionFunction, SanitizedCollectionConfig, SanitizedConfig, + WithServerSideProps as WithServerSidePropsType, } from 'payload/types' import { ViewDescription } from '@payloadcms/ui/elements/ViewDescription' @@ -21,6 +22,7 @@ import { mapFields } from './fields.js' export const mapCollections = ({ DefaultEditView, DefaultListView, + WithServerSideProps, collections, config, i18n, @@ -28,6 +30,7 @@ export const mapCollections = ({ }: { DefaultEditView: React.FC DefaultListView: React.FC + WithServerSideProps: WithServerSidePropsType collections: SanitizedCollectionConfig[] config: SanitizedConfig i18n: I18n @@ -71,44 +74,60 @@ export const mapCollections = ({ const List = CustomListView || DefaultListView - const beforeList = collectionConfig?.admin?.components?.BeforeList - - const beforeListTable = collectionConfig?.admin?.components?.BeforeListTable - - const afterList = collectionConfig?.admin?.components?.AfterList - - const afterListTable = collectionConfig?.admin?.components?.AfterListTable - const SaveButtonComponent = collectionConfig?.admin?.components?.edit?.SaveButton - const SaveButton = SaveButtonComponent ? : undefined + + const SaveButton = SaveButtonComponent ? ( + + ) : undefined const SaveDraftButtonComponent = collectionConfig?.admin?.components?.edit?.SaveDraftButton - const SaveDraftButton = SaveDraftButtonComponent ? : undefined + + const SaveDraftButton = SaveDraftButtonComponent ? ( + + ) : undefined const PreviewButtonComponent = collectionConfig?.admin?.components?.edit?.PreviewButton - const PreviewButton = PreviewButtonComponent ? : undefined + + const PreviewButton = PreviewButtonComponent ? ( + + ) : undefined const PublishButtonComponent = collectionConfig?.admin?.components?.edit?.PublishButton - const PublishButton = PublishButtonComponent ? : undefined + + const PublishButton = PublishButtonComponent ? ( + + ) : undefined + + const beforeList = collectionConfig?.admin?.components?.BeforeList const BeforeList = - (beforeList && Array.isArray(beforeList) && beforeList?.map((Component) => )) || + (beforeList && + Array.isArray(beforeList) && + beforeList?.map((Component) => )) || null + const beforeListTable = collectionConfig?.admin?.components?.BeforeListTable + const BeforeListTable = (beforeListTable && Array.isArray(beforeListTable) && - beforeListTable?.map((Component) => )) || + beforeListTable?.map((Component) => )) || null + const afterList = collectionConfig?.admin?.components?.AfterList + const AfterList = - (afterList && Array.isArray(afterList) && afterList?.map((Component) => )) || + (afterList && + Array.isArray(afterList) && + afterList?.map((Component) => )) || null + const afterListTable = collectionConfig?.admin?.components?.AfterListTable + const AfterListTable = (afterListTable && Array.isArray(afterListTable) && - afterListTable?.map((Component) => )) || + afterListTable?.map((Component) => )) || null const descriptionProps: ViewDescriptionProps = { @@ -134,7 +153,7 @@ export const mapCollections = ({ const Description = DescriptionComponent !== undefined ? ( - + ) : undefined const componentMap: CollectionComponentMap = { @@ -150,9 +169,11 @@ export const mapCollections = ({ SaveButton, SaveDraftButton, actionsMap: mapActions({ + WithServerSideProps, collectionConfig, }), fieldMap: mapFields({ + WithServerSideProps, config, fieldSchema: fields, i18n, diff --git a/packages/ui/src/providers/ComponentMap/buildComponentMap/fields.tsx b/packages/ui/src/providers/ComponentMap/buildComponentMap/fields.tsx index c55bd4b43..d09d3cc30 100644 --- a/packages/ui/src/providers/ComponentMap/buildComponentMap/fields.tsx +++ b/packages/ui/src/providers/ComponentMap/buildComponentMap/fields.tsx @@ -1,4 +1,5 @@ import type { I18n } from '@payloadcms/translations' +import type { CustomComponent } from 'packages/payload/src/config/types.js' import type { CellComponentProps, DescriptionComponent, @@ -11,11 +12,17 @@ import type { Option, RowLabelComponent, SanitizedConfig, + WithServerSideProps as WithServerSidePropsType, } from 'payload/types' import { FieldDescription } from '@payloadcms/ui/forms/FieldDescription' import { fieldAffectsData, fieldIsPresentationalOnly } from 'payload/types' -import { isPlainFunction, isReactComponent } from 'payload/utilities' +import { + isPlainFunction, + isReactClientComponent, + isReactComponent, + isReactServerComponent, +} from 'payload/utilities' import React, { Fragment } from 'react' import type { ArrayFieldProps } from '../../../fields/Array/index.js' @@ -50,6 +57,7 @@ import type { import { HiddenInput } from '../../../fields/HiddenInput/index.js' export const mapFields = (args: { + WithServerSideProps: WithServerSidePropsType config: SanitizedConfig /** * If mapFields is used outside of collections, you might not want it to add an id field @@ -62,6 +70,7 @@ export const mapFields = (args: { readOnly?: boolean }): FieldMap => { const { + WithServerSideProps, config, disableAddingID, fieldSchema, @@ -74,8 +83,7 @@ export const mapFields = (args: { const result: FieldMap = fieldSchema.reduce((acc, field): FieldMap => { const fieldIsPresentational = fieldIsPresentationalOnly(field) - let CustomFieldComponent: React.ComponentType = - field.admin?.components?.Field + let CustomFieldComponent: CustomComponent = field.admin?.components?.Field const CustomCellComponent = field.admin?.components?.Cell @@ -102,7 +110,7 @@ export const mapFields = (args: { Array.isArray(field.admin?.components?.afterInput) && ( {field.admin.components.afterInput.map((Component, i) => ( - + ))} )) || @@ -115,7 +123,7 @@ export const mapFields = (args: { Array.isArray(field.admin.components.beforeInput) && ( {field.admin.components.beforeInput.map((Component, i) => ( - + ))} )) || @@ -133,12 +141,12 @@ export const mapFields = (args: { ('admin' in field && field.admin?.components && 'Label' in field.admin.components && - field.admin?.components?.Label) || + field.admin.components?.Label) || undefined const CustomLabel = CustomLabelComponent !== undefined ? ( - + ) : undefined const descriptionProps: FieldDescriptionProps = { @@ -164,7 +172,10 @@ export const mapFields = (args: { const CustomDescription = CustomDescriptionComponent !== undefined ? ( - + ) : undefined const errorProps = { @@ -180,7 +191,7 @@ export const mapFields = (args: { const CustomError = CustomErrorComponent !== undefined ? ( - + ) : undefined const baseFieldProps: FormFieldBase = { @@ -239,7 +250,7 @@ export const mapFields = (args: { isReactComponent(field.admin.components.RowLabel) ) { const CustomRowLabelComponent = field.admin.components.RowLabel - CustomRowLabel = + CustomRowLabel = } const arrayFieldProps: Omit = { @@ -249,6 +260,7 @@ export const mapFields = (args: { className: field.admin?.className, disabled: field.admin?.disabled, fieldMap: mapFields({ + WithServerSideProps, config, fieldSchema: field.fields, filter, @@ -272,6 +284,7 @@ export const mapFields = (args: { case 'blocks': { const blocks = field.blocks.map((block) => { const blockFieldMap = mapFields({ + WithServerSideProps, config, fieldSchema: block.fields, filter, @@ -355,7 +368,9 @@ export const mapFields = (args: { if (isReactComponent(field.label) || isPlainFunction(field.label)) { const CustomCollapsibleLabelComponent = field.label as RowLabelComponent - CustomCollapsibleLabel = + CustomCollapsibleLabel = ( + + ) } const collapsibleField: Omit = { @@ -364,6 +379,7 @@ export const mapFields = (args: { className: field.admin?.className, disabled: field.admin?.disabled, fieldMap: mapFields({ + WithServerSideProps, config, disableAddingID: true, fieldSchema: field.fields, @@ -426,6 +442,7 @@ export const mapFields = (args: { className: field.admin?.className, disabled: field.admin?.disabled, fieldMap: mapFields({ + WithServerSideProps, config, disableAddingID: true, fieldSchema: field.fields, @@ -553,7 +570,12 @@ export const mapFields = (args: { const RichTextCellComponent = field.editor.CellComponent if (typeof field.editor.generateComponentMap === 'function') { - const result = field.editor.generateComponentMap({ config, i18n, schemaPath: path }) + const result = field.editor.generateComponentMap({ + WithServerSideProps, + config, + i18n, + schemaPath: path, + }) richTextField.richTextComponentMap = result cellComponentProps.richTextComponentMap = result } @@ -563,7 +585,9 @@ export const mapFields = (args: { } if (RichTextCellComponent) { - cellComponentProps.CellComponentOverride = + cellComponentProps.CellComponentOverride = ( + + ) } fieldComponentProps = richTextField @@ -576,6 +600,7 @@ export const mapFields = (args: { className: field.admin?.className, disabled: field.admin?.disabled, fieldMap: mapFields({ + WithServerSideProps, config, disableAddingID: true, fieldSchema: field.fields, @@ -597,6 +622,7 @@ export const mapFields = (args: { // `tabs` fields require a field map of each of its tab's nested fields const tabs = field.tabs.map((tab) => { const tabFieldMap = mapFields({ + WithServerSideProps, config, disableAddingID: true, fieldSchema: tab.fields, @@ -722,10 +748,10 @@ export const mapFields = (args: { name: 'name' in field ? field.name : undefined, type: field.type, CustomCell: CustomCellComponent ? ( - + ) : undefined, CustomField: CustomFieldComponent ? ( - + ) : undefined, cellComponentProps, disableBulkEdit: diff --git a/packages/ui/src/providers/ComponentMap/buildComponentMap/globals.tsx b/packages/ui/src/providers/ComponentMap/buildComponentMap/globals.tsx index 1a78700e0..10d44da0f 100644 --- a/packages/ui/src/providers/ComponentMap/buildComponentMap/globals.tsx +++ b/packages/ui/src/providers/ComponentMap/buildComponentMap/globals.tsx @@ -5,6 +5,7 @@ import type { EntityDescriptionFunction, SanitizedConfig, SanitizedGlobalConfig, + WithServerSideProps as WithServerSidePropsType, } from 'payload/types' import { ViewDescription, type ViewDescriptionProps } from '@payloadcms/ui/elements/ViewDescription' @@ -18,12 +19,14 @@ import { mapFields } from './fields.js' export const mapGlobals = ({ DefaultEditView, + WithServerSideProps, config, globals, i18n, readOnly: readOnlyOverride, }: { DefaultEditView: React.FC + WithServerSideProps: WithServerSidePropsType config: SanitizedConfig globals: SanitizedGlobalConfig[] i18n: I18n @@ -37,16 +40,28 @@ export const mapGlobals = ({ const editViewFromConfig = globalConfig?.admin?.components?.views?.Edit const SaveButton = globalConfig?.admin?.components?.elements?.SaveButton - const SaveButtonComponent = SaveButton ? : undefined + + const SaveButtonComponent = SaveButton ? ( + + ) : undefined const SaveDraftButton = globalConfig?.admin?.components?.elements?.SaveDraftButton - const SaveDraftButtonComponent = SaveDraftButton ? : undefined + + const SaveDraftButtonComponent = SaveDraftButton ? ( + + ) : undefined const PreviewButton = globalConfig?.admin?.components?.elements?.PreviewButton - const PreviewButtonComponent = PreviewButton ? : undefined + + const PreviewButtonComponent = PreviewButton ? ( + + ) : undefined const PublishButton = globalConfig?.admin?.components?.elements?.PublishButton - const PublishButtonComponent = PublishButton ? : undefined + + const PublishButtonComponent = PublishButton ? ( + + ) : undefined const CustomEditView = typeof editViewFromConfig === 'function' @@ -84,7 +99,7 @@ export const mapGlobals = ({ const Description = DescriptionComponent !== undefined ? ( - + ) : undefined const componentMap: GlobalComponentMap = { @@ -95,9 +110,11 @@ export const mapGlobals = ({ SaveButton: SaveButtonComponent, SaveDraftButton: SaveDraftButtonComponent, actionsMap: mapActions({ + WithServerSideProps, globalConfig, }), fieldMap: mapFields({ + WithServerSideProps, config, fieldSchema: fields, i18n, diff --git a/packages/ui/src/providers/ComponentMap/buildComponentMap/index.tsx b/packages/ui/src/providers/ComponentMap/buildComponentMap/index.tsx index 954a8c2b8..8abade7e5 100644 --- a/packages/ui/src/providers/ComponentMap/buildComponentMap/index.tsx +++ b/packages/ui/src/providers/ComponentMap/buildComponentMap/index.tsx @@ -1,10 +1,17 @@ import type { I18n } from '@payloadcms/translations' -import type { AdminViewProps, EditViewProps, SanitizedConfig } from 'payload/types' +import type { + AdminViewProps, + EditViewProps, + Payload, + SanitizedConfig, + WithServerSideProps as WithServerSidePropsType, +} from 'payload/types' import React from 'react' import type { ComponentMap } from './types.js' +import { WithServerSideProps as WithServerSidePropsGeneric } from './WithServerSideProps.js' import { mapCollections } from './collections.js' import { mapGlobals } from './globals.js' @@ -14,16 +21,22 @@ export const buildComponentMap = (args: { children: React.ReactNode config: SanitizedConfig i18n: I18n + payload: Payload readOnly?: boolean }): { componentMap: ComponentMap wrappedChildren: React.ReactNode } => { - const { DefaultEditView, DefaultListView, children, config, i18n, readOnly } = args + const { DefaultEditView, DefaultListView, children, config, i18n, payload, readOnly } = args + + const WithServerSideProps: WithServerSidePropsType = ({ Component, ...rest }) => { + return + } const collections = mapCollections({ DefaultEditView, DefaultListView, + WithServerSideProps, collections: config.collections, config, i18n, @@ -32,6 +45,7 @@ export const buildComponentMap = (args: { const globals = mapGlobals({ DefaultEditView, + WithServerSideProps, config, globals: config.globals, i18n, @@ -52,17 +66,21 @@ export const buildComponentMap = (args: { const LogoutButtonComponent = config.admin?.components?.logout?.Button - const LogoutButton = LogoutButtonComponent ? : null + const LogoutButton = LogoutButtonComponent ? ( + + ) : null const IconComponent = config.admin?.components?.graphics?.Icon - const Icon = IconComponent ? : null + const Icon = IconComponent ? : null return { componentMap: { Icon, LogoutButton, - actions: config.admin?.components?.actions?.map((Component) => ), + actions: config.admin?.components?.actions?.map((Component) => ( + + )), collections, globals, }, diff --git a/packages/ui/src/providers/ComponentMap/index.tsx b/packages/ui/src/providers/ComponentMap/index.tsx index 79e0b4efb..e0b378532 100644 --- a/packages/ui/src/providers/ComponentMap/index.tsx +++ b/packages/ui/src/providers/ComponentMap/index.tsx @@ -17,6 +17,8 @@ export type IComponentMapContext = { }) => MappedField | undefined } +export { WithServerSideProps } from './buildComponentMap/WithServerSideProps.js' + const ComponentMapContext = createContext({} as IComponentMapContext) export const ComponentMapProvider: React.FC<{ diff --git a/test/admin/components/AdminButton/index.tsx b/test/admin/components/AdminButton/index.tsx index 459d2c8a2..5cd5438e6 100644 --- a/test/admin/components/AdminButton/index.tsx +++ b/test/admin/components/AdminButton/index.tsx @@ -1,8 +1,10 @@ +import type { SanitizedConfig } from 'payload/types' + import React from 'react' const baseClass = 'admin-button' -export const AdminButton: React.FC = () => { +export const AdminButton: SanitizedConfig['admin']['components']['actions'][0] = () => { return (
{ +export const AfterDashboard: SanitizedConfig['admin']['components']['afterDashboard'][0] = () => { return (

Test Config

diff --git a/test/admin/components/AfterNavLinks/index.tsx b/test/admin/components/AfterNavLinks/index.tsx index 84ecfb99b..cb881f215 100644 --- a/test/admin/components/AfterNavLinks/index.tsx +++ b/test/admin/components/AfterNavLinks/index.tsx @@ -1,5 +1,7 @@ 'use client' +import type { SanitizedConfig } from 'payload/types' + import { useConfig } from '@payloadcms/ui/providers/Config' import LinkImport from 'next/link.js' const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default @@ -8,7 +10,7 @@ import React from 'react' const baseClass = 'after-nav-links' -export const AfterNavLinks: React.FC = () => { +export const AfterNavLinks: SanitizedConfig['admin']['components']['afterNavLinks'][0] = () => { const { routes: { admin: adminRoute }, } = useConfig() diff --git a/test/admin/components/BeforeLogin/index.tsx b/test/admin/components/BeforeLogin/index.tsx index d3be5424c..c9be4fb00 100644 --- a/test/admin/components/BeforeLogin/index.tsx +++ b/test/admin/components/BeforeLogin/index.tsx @@ -1,10 +1,13 @@ 'use client' +import type { SanitizedConfig } from 'payload/types' + import { useTranslation } from '@payloadcms/ui/providers/Translation' import React from 'react' -export const BeforeLogin: React.FC = () => { +export const BeforeLogin: SanitizedConfig['admin']['components']['beforeLogin'][0] = () => { const translation = useTranslation() + return (

{translation.t('general:welcome')}

diff --git a/test/admin/components/CollectionAPIButton/index.tsx b/test/admin/components/CollectionAPIButton/index.tsx index 3d15673dc..b4f8ac915 100644 --- a/test/admin/components/CollectionAPIButton/index.tsx +++ b/test/admin/components/CollectionAPIButton/index.tsx @@ -1,8 +1,10 @@ +import type { CustomComponent } from 'payload/config' + import React from 'react' const baseClass = 'collection-api-button' -export const CollectionAPIButton: React.FC = () => { +export const CollectionAPIButton: CustomComponent = () => { return (
{ +export const CollectionEditButton: CustomComponent = () => { return (
{ +export const CollectionListButton: CustomComponent = () => { return (