fix(next): pass req through document tab conditions and custom server components (#13302)
Custom document tab components (server components) do not receive the `user` prop, as the types suggest. This makes it difficult to wire up conditional rendering based on the user. This is because tab conditions don't receive a user argument either, forcing you to render the default tab component yourself—but a custom component should not be needed for this in the first place. Now they both receive `req` alongside `user`, which is more closely aligned with custom field components. --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1210906078627357
This commit is contained in:
@@ -1,4 +1,11 @@
|
||||
import type { DocumentTabConfig, DocumentTabServerProps, ServerProps } from 'payload'
|
||||
import type {
|
||||
DocumentTabConfig,
|
||||
DocumentTabServerPropsOnly,
|
||||
PayloadRequest,
|
||||
SanitizedCollectionConfig,
|
||||
SanitizedGlobalConfig,
|
||||
SanitizedPermissions,
|
||||
} from 'payload'
|
||||
import type React from 'react'
|
||||
|
||||
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
|
||||
@@ -9,27 +16,24 @@ import './index.scss'
|
||||
|
||||
export const baseClass = 'doc-tab'
|
||||
|
||||
export const DocumentTab: React.FC<
|
||||
{ readonly Pill_Component?: React.FC } & DocumentTabConfig & DocumentTabServerProps
|
||||
> = (props) => {
|
||||
export const DefaultDocumentTab: React.FC<{
|
||||
apiURL?: string
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
globalConfig?: SanitizedGlobalConfig
|
||||
path?: string
|
||||
permissions?: SanitizedPermissions
|
||||
req: PayloadRequest
|
||||
tabConfig: { readonly Pill_Component?: React.FC } & DocumentTabConfig
|
||||
}> = (props) => {
|
||||
const {
|
||||
apiURL,
|
||||
collectionConfig,
|
||||
globalConfig,
|
||||
href: tabHref,
|
||||
i18n,
|
||||
isActive: tabIsActive,
|
||||
label,
|
||||
newTab,
|
||||
payload,
|
||||
permissions,
|
||||
Pill,
|
||||
Pill_Component,
|
||||
req,
|
||||
tabConfig: { href: tabHref, isActive: tabIsActive, label, newTab, Pill, Pill_Component },
|
||||
} = props
|
||||
|
||||
const { config } = payload
|
||||
const { routes } = config
|
||||
|
||||
let href = typeof tabHref === 'string' ? tabHref : ''
|
||||
let isActive = typeof tabIsActive === 'boolean' ? tabIsActive : false
|
||||
|
||||
@@ -38,7 +42,7 @@ export const DocumentTab: React.FC<
|
||||
apiURL,
|
||||
collection: collectionConfig,
|
||||
global: globalConfig,
|
||||
routes,
|
||||
routes: req.payload.config.routes,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -51,13 +55,13 @@ export const DocumentTab: React.FC<
|
||||
const labelToRender =
|
||||
typeof label === 'function'
|
||||
? label({
|
||||
t: i18n.t,
|
||||
t: req.i18n.t,
|
||||
})
|
||||
: label
|
||||
|
||||
return (
|
||||
<DocumentTabLink
|
||||
adminRoute={routes.admin}
|
||||
adminRoute={req.payload.config.routes.admin}
|
||||
ariaLabel={labelToRender}
|
||||
baseClass={baseClass}
|
||||
href={href}
|
||||
@@ -72,12 +76,14 @@ export const DocumentTab: React.FC<
|
||||
{RenderServerComponent({
|
||||
Component: Pill,
|
||||
Fallback: Pill_Component,
|
||||
importMap: payload.importMap,
|
||||
importMap: req.payload.importMap,
|
||||
serverProps: {
|
||||
i18n,
|
||||
payload,
|
||||
i18n: req.i18n,
|
||||
payload: req.payload,
|
||||
permissions,
|
||||
} satisfies ServerProps,
|
||||
req,
|
||||
user: req.user,
|
||||
} satisfies DocumentTabServerPropsOnly,
|
||||
})}
|
||||
</Fragment>
|
||||
) : null}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { I18n } from '@payloadcms/translations'
|
||||
import type {
|
||||
DocumentTabClientProps,
|
||||
DocumentTabServerPropsOnly,
|
||||
Payload,
|
||||
PayloadRequest,
|
||||
SanitizedCollectionConfig,
|
||||
SanitizedGlobalConfig,
|
||||
SanitizedPermissions,
|
||||
@@ -12,7 +11,7 @@ import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerCompo
|
||||
import React from 'react'
|
||||
|
||||
import { ShouldRenderTabs } from './ShouldRenderTabs.js'
|
||||
import { DocumentTab } from './Tab/index.js'
|
||||
import { DefaultDocumentTab } from './Tab/index.js'
|
||||
import { getTabs } from './tabs/index.js'
|
||||
import './index.scss'
|
||||
|
||||
@@ -21,12 +20,10 @@ const baseClass = 'doc-tabs'
|
||||
export const DocumentTabs: React.FC<{
|
||||
collectionConfig: SanitizedCollectionConfig
|
||||
globalConfig: SanitizedGlobalConfig
|
||||
i18n: I18n
|
||||
payload: Payload
|
||||
permissions: SanitizedPermissions
|
||||
}> = (props) => {
|
||||
const { collectionConfig, globalConfig, i18n, payload, permissions } = props
|
||||
const { config } = payload
|
||||
req: PayloadRequest
|
||||
}> = ({ collectionConfig, globalConfig, permissions, req }) => {
|
||||
const { config } = req.payload
|
||||
|
||||
const tabs = getTabs({
|
||||
collectionConfig,
|
||||
@@ -38,42 +35,46 @@ export const DocumentTabs: React.FC<{
|
||||
<div className={baseClass}>
|
||||
<div className={`${baseClass}__tabs-container`}>
|
||||
<ul className={`${baseClass}__tabs`}>
|
||||
{tabs?.map(({ tab, viewPath }, index) => {
|
||||
const { condition } = tab || {}
|
||||
{tabs?.map(({ tab: tabConfig, viewPath }, index) => {
|
||||
const { condition } = tabConfig || {}
|
||||
|
||||
const meetsCondition =
|
||||
!condition || condition({ collectionConfig, config, globalConfig, permissions })
|
||||
!condition ||
|
||||
condition({ collectionConfig, config, globalConfig, permissions, req })
|
||||
|
||||
if (!meetsCondition) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (tab?.Component) {
|
||||
if (tabConfig?.Component) {
|
||||
return RenderServerComponent({
|
||||
clientProps: {
|
||||
path: viewPath,
|
||||
} satisfies DocumentTabClientProps,
|
||||
Component: tab.Component,
|
||||
importMap: payload.importMap,
|
||||
Component: tabConfig.Component,
|
||||
importMap: req.payload.importMap,
|
||||
key: `tab-${index}`,
|
||||
serverProps: {
|
||||
collectionConfig,
|
||||
globalConfig,
|
||||
i18n,
|
||||
payload,
|
||||
i18n: req.i18n,
|
||||
payload: req.payload,
|
||||
permissions,
|
||||
req,
|
||||
user: req.user,
|
||||
} satisfies DocumentTabServerPropsOnly,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<DocumentTab
|
||||
<DefaultDocumentTab
|
||||
collectionConfig={collectionConfig}
|
||||
globalConfig={globalConfig}
|
||||
key={`tab-${index}`}
|
||||
path={viewPath}
|
||||
{...{
|
||||
...props,
|
||||
...tab,
|
||||
}}
|
||||
permissions={permissions}
|
||||
req={req}
|
||||
tabConfig={tabConfig}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { I18n } from '@payloadcms/translations'
|
||||
import type {
|
||||
Payload,
|
||||
PayloadRequest,
|
||||
SanitizedCollectionConfig,
|
||||
SanitizedGlobalConfig,
|
||||
SanitizedPermissions,
|
||||
@@ -18,11 +18,10 @@ export const DocumentHeader: React.FC<{
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
globalConfig?: SanitizedGlobalConfig
|
||||
hideTabs?: boolean
|
||||
i18n: I18n
|
||||
payload: Payload
|
||||
permissions: SanitizedPermissions
|
||||
req: PayloadRequest
|
||||
}> = (props) => {
|
||||
const { collectionConfig, globalConfig, hideTabs, i18n, payload, permissions } = props
|
||||
const { collectionConfig, globalConfig, hideTabs, permissions, req } = props
|
||||
|
||||
return (
|
||||
<Gutter className={baseClass}>
|
||||
@@ -31,9 +30,8 @@ export const DocumentHeader: React.FC<{
|
||||
<DocumentTabs
|
||||
collectionConfig={collectionConfig}
|
||||
globalConfig={globalConfig}
|
||||
i18n={i18n}
|
||||
payload={payload}
|
||||
permissions={permissions}
|
||||
req={req}
|
||||
/>
|
||||
)}
|
||||
</Gutter>
|
||||
|
||||
@@ -137,9 +137,8 @@ export async function Account({ initPageResult, params, searchParams }: AdminVie
|
||||
<DocumentHeader
|
||||
collectionConfig={collectionConfig}
|
||||
hideTabs
|
||||
i18n={i18n}
|
||||
payload={payload}
|
||||
permissions={permissions}
|
||||
req={req}
|
||||
/>
|
||||
<HydrateAuthProvider permissions={permissions} />
|
||||
{RenderServerComponent({
|
||||
|
||||
@@ -416,9 +416,8 @@ export const renderDocument = async ({
|
||||
<DocumentHeader
|
||||
collectionConfig={collectionConfig}
|
||||
globalConfig={globalConfig}
|
||||
i18n={i18n}
|
||||
payload={payload}
|
||||
permissions={permissions}
|
||||
req={req}
|
||||
/>
|
||||
)}
|
||||
<HydrateAuthProvider permissions={permissions} />
|
||||
|
||||
@@ -68,6 +68,9 @@ export type FieldPaths = {
|
||||
path: string
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: This should be renamed to `FieldComponentServerProps` or similar
|
||||
*/
|
||||
export type ServerComponentProps = {
|
||||
clientField: ClientFieldWithOptionalType
|
||||
clientFieldSchemaMap: ClientFieldSchemaMap
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { SanitizedPermissions } from '../../auth/types.js'
|
||||
import type { SanitizedCollectionConfig } from '../../collections/config/types.js'
|
||||
import type { PayloadComponent, SanitizedConfig, ServerProps } from '../../config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../../globals/config/types.js'
|
||||
import type { PayloadRequest } from '../../types/index.js'
|
||||
import type { Data, DocumentSlots, FormState } from '../types.js'
|
||||
import type { InitPageResult, ViewTypes } from './index.js'
|
||||
|
||||
@@ -50,6 +51,7 @@ export type DocumentTabServerPropsOnly = {
|
||||
readonly collectionConfig?: SanitizedCollectionConfig
|
||||
readonly globalConfig?: SanitizedGlobalConfig
|
||||
readonly permissions: SanitizedPermissions
|
||||
readonly req: PayloadRequest
|
||||
} & ServerProps
|
||||
|
||||
export type DocumentTabClientProps = {
|
||||
@@ -60,9 +62,13 @@ export type DocumentTabServerProps = DocumentTabClientProps & DocumentTabServerP
|
||||
|
||||
export type DocumentTabCondition = (args: {
|
||||
collectionConfig: SanitizedCollectionConfig
|
||||
/**
|
||||
* @deprecated: Use `req.payload.config` instead. This will be removed in v4.
|
||||
*/
|
||||
config: SanitizedConfig
|
||||
globalConfig: SanitizedGlobalConfig
|
||||
permissions: SanitizedPermissions
|
||||
req: PayloadRequest
|
||||
}) => boolean
|
||||
|
||||
// Everything is optional because we merge in the defaults
|
||||
|
||||
Reference in New Issue
Block a user