From 1c1847f63ceab834d0bade9331e7c720db2da6cd Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Mon, 25 Mar 2024 18:08:08 -0400 Subject: [PATCH] fix(next): dynamic params for custom collection and global views --- packages/next/src/views/Account/index.tsx | 7 +- ...iewByPath.tsx => getCustomViewByRoute.tsx} | 23 +- .../src/views/Document/getViewsFromConfig.tsx | 282 +++++++++++------- packages/next/src/views/Document/index.tsx | 12 +- .../src/views/Root/getCustomViewByRoute.tsx | 23 +- .../src/views/Root/isPathMatchingRoute.ts | 30 ++ packages/payload/src/admin/views/types.ts | 1 + .../elements/DocumentHeader/Tabs/index.tsx | 2 +- test/admin/collections/CustomViews2.ts | 23 +- .../components/CustomTabComponent/client.tsx | 17 +- .../components/views/CustomDefault/index.scss | 2 +- .../components/views/CustomMinimal/index.scss | 2 +- .../index.tsx | 6 +- .../{CustomTab => CustomTabLabel}/index.tsx | 6 +- .../views/CustomTabWithParam/index.tsx | 24 ++ test/admin/globals/CustomViews2.ts | 7 +- test/admin/shared.ts | 8 +- tsconfig.json | 2 +- 18 files changed, 310 insertions(+), 167 deletions(-) rename packages/next/src/views/Document/{getCustomViewByPath.tsx => getCustomViewByRoute.tsx} (57%) create mode 100644 packages/next/src/views/Root/isPathMatchingRoute.ts rename test/admin/components/views/{CustomTab2 => CustomTabComponent}/index.tsx (77%) rename test/admin/components/views/{CustomTab => CustomTabLabel}/index.tsx (78%) create mode 100644 test/admin/components/views/CustomTabWithParam/index.tsx diff --git a/packages/next/src/views/Account/index.tsx b/packages/next/src/views/Account/index.tsx index 648f4740e..9a21fee75 100644 --- a/packages/next/src/views/Account/index.tsx +++ b/packages/next/src/views/Account/index.tsx @@ -17,7 +17,11 @@ import { Settings } from './Settings/index.js' export { generateAccountMetadata } from './meta.js' -export const Account: React.FC = async ({ initPageResult, searchParams }) => { +export const Account: React.FC = async ({ + initPageResult, + params, + searchParams, +}) => { const { locale, permissions, @@ -87,6 +91,7 @@ export const Account: React.FC = async ({ initPageResult, search const viewComponentProps: ServerSideEditViewProps = { initPageResult, + params, routeSegments: [], searchParams, } diff --git a/packages/next/src/views/Document/getCustomViewByPath.tsx b/packages/next/src/views/Document/getCustomViewByRoute.tsx similarity index 57% rename from packages/next/src/views/Document/getCustomViewByPath.tsx rename to packages/next/src/views/Document/getCustomViewByRoute.tsx index 82519a7a9..2abb34f50 100644 --- a/packages/next/src/views/Document/getCustomViewByPath.tsx +++ b/packages/next/src/views/Document/getCustomViewByRoute.tsx @@ -1,16 +1,29 @@ import type { EditViewComponent } from 'payload/config' import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload/types' -export const getCustomViewByPath = ( +import { isPathMatchingRoute } from '../Root/isPathMatchingRoute.js' + +export const getCustomViewByRoute = ({ + baseRoute, + currentRoute, + views, +}: { + baseRoute: string + currentRoute: string views: | SanitizedCollectionConfig['admin']['components']['views'] - | SanitizedGlobalConfig['admin']['components']['views'], - path: string, -): EditViewComponent => { + | SanitizedGlobalConfig['admin']['components']['views'] +}): EditViewComponent => { if (typeof views?.Edit === 'object' && typeof views?.Edit !== 'function') { const foundViewConfig = Object.entries(views.Edit).find(([, view]) => { if (typeof view === 'object' && typeof view !== 'function' && 'path' in view) { - return view.path === path + const viewPath = `${baseRoute}${view.path}` + + return isPathMatchingRoute({ + currentRoute, + exact: true, + path: viewPath, + }) } return false })?.[1] diff --git a/packages/next/src/views/Document/getViewsFromConfig.tsx b/packages/next/src/views/Document/getViewsFromConfig.tsx index 673312516..aa4e92701 100644 --- a/packages/next/src/views/Document/getViewsFromConfig.tsx +++ b/packages/next/src/views/Document/getViewsFromConfig.tsx @@ -8,15 +8,17 @@ import type { } from 'payload/types' import { isEntityHidden } from 'payload/utilities' +import React from 'react' import { APIView as DefaultAPIView } from '../API/index.js' import { EditView as DefaultEditView } from '../Edit/index.js' import { LivePreviewView as DefaultLivePreviewView } from '../LivePreview/index.js' +import { NotFoundClient } from '../NotFound/index.client.js' import { Unauthorized } from '../Unauthorized/index.js' import { VersionView as DefaultVersionView } from '../Version/index.js' import { VersionsView as DefaultVersionsView } from '../Versions/index.js' import { getCustomViewByKey } from './getCustomViewByKey.js' -import { getCustomViewByPath } from './getCustomViewByPath.js' +import { getCustomViewByRoute } from './getCustomViewByRoute.js' export const getViewsFromConfig = ({ collectionConfig, @@ -46,6 +48,10 @@ export const getViewsFromConfig = ({ let CustomView: EditViewComponent = null let ErrorView: AdminViewComponent = null + const { + routes: { admin: adminRoute }, + } = config + const views = (collectionConfig && collectionConfig?.admin?.components?.views) || (globalConfig && globalConfig?.admin?.components?.views) @@ -65,8 +71,7 @@ export const getViewsFromConfig = ({ } if (!EditOverride) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [collectionEntity, collectionSlug, createOrID, nestedViewSlug, segmentFive] = + const [collectionEntity, collectionSlug, segment3, segment4, segment5, ...remainingSegments] = routeSegments const { @@ -78,74 +83,110 @@ export const getViewsFromConfig = ({ } // `../:id`, or `../create` - if (routeSegments.length === 3) { - switch (createOrID) { - case 'create': { - if ('create' in docPermissions && docPermissions?.create?.permission) { - CustomView = getCustomViewByKey(views, 'Default') - DefaultView = DefaultEditView - } else { - ErrorView = Unauthorized + switch (routeSegments.length) { + case 3: { + switch (segment3) { + case 'create': { + if ('create' in docPermissions && docPermissions?.create?.permission) { + CustomView = getCustomViewByKey(views, 'Default') + DefaultView = DefaultEditView + } else { + ErrorView = Unauthorized + } + break } - break - } - default: { - if (docPermissions?.read?.permission) { - CustomView = getCustomViewByKey(views, 'Default') - DefaultView = DefaultEditView - } else { - ErrorView = Unauthorized + default: { + if (docPermissions?.read?.permission) { + CustomView = getCustomViewByKey(views, 'Default') + DefaultView = DefaultEditView + } else { + ErrorView = Unauthorized + } + break } } + break } - } - // `../:id/api`, `../:id/preview`, `../:id/versions`, etc - if (routeSegments?.length === 4) { - switch (nestedViewSlug) { - case 'api': { - if (collectionConfig?.admin?.hideAPIURL !== true) { - CustomView = getCustomViewByKey(views, 'API') - DefaultView = DefaultAPIView + // `../:id/api`, `../:id/preview`, `../:id/versions`, etc + case 4: { + switch (segment4) { + case 'api': { + if (collectionConfig?.admin?.hideAPIURL !== true) { + CustomView = getCustomViewByKey(views, 'API') + DefaultView = DefaultAPIView + } + break } - break - } - case 'preview': { - if (livePreviewEnabled) { - DefaultView = DefaultLivePreviewView + case 'preview': { + if (livePreviewEnabled) { + DefaultView = DefaultLivePreviewView + } + break } - break - } - case 'versions': { + case 'versions': { + if (docPermissions?.readVersions?.permission) { + CustomView = getCustomViewByKey(views, 'Versions') + DefaultView = DefaultVersionsView + } else { + ErrorView = Unauthorized + } + break + } + + default: { + const baseRoute = [adminRoute, 'collections', collectionSlug, segment3] + .filter(Boolean) + .join('/') + + const currentRoute = [baseRoute, segment4, segment5, ...remainingSegments] + .filter(Boolean) + .join('/') + + CustomView = getCustomViewByRoute({ + baseRoute, + currentRoute, + views, + }) + + if (!CustomView) ErrorView = () => + + break + } + } + break + } + + // `../:id/versions/:version`, etc + default: { + if (segment4 === 'versions') { if (docPermissions?.readVersions?.permission) { - CustomView = getCustomViewByKey(views, 'Versions') - DefaultView = DefaultVersionsView + CustomView = getCustomViewByKey(views, 'Version') + DefaultView = DefaultVersionView } else { ErrorView = Unauthorized } - break - } - - default: { - const path = `/${nestedViewSlug}` - CustomView = getCustomViewByPath(views, path) - break - } - } - } - - // `../:id/versions/:version`, etc - if (routeSegments.length === 5) { - if (nestedViewSlug === 'versions') { - if (docPermissions?.readVersions?.permission) { - CustomView = getCustomViewByKey(views, 'Version') - DefaultView = DefaultVersionView } else { - ErrorView = Unauthorized + const baseRoute = [adminRoute, collectionEntity, collectionSlug, segment3] + .filter(Boolean) + .join('/') + + const currentRoute = [baseRoute, segment4, segment5, ...remainingSegments] + .filter(Boolean) + .join('/') + + CustomView = getCustomViewByRoute({ + baseRoute, + currentRoute, + views, + }) + + if (!CustomView) ErrorView = () => } + break } } } @@ -161,7 +202,7 @@ export const getViewsFromConfig = ({ if (!EditOverride) { // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [globalEntity, globalSlug, nestedViewSlug] = routeSegments + const [globalEntity, globalSlug, segment3, ...remainingSegments] = routeSegments const { admin: { hidden }, @@ -171,64 +212,83 @@ export const getViewsFromConfig = ({ return null } - if (routeSegments?.length === 2) { - if (docPermissions?.read?.permission) { - CustomView = getCustomViewByKey(views, 'Default') - DefaultView = DefaultEditView - } else { - ErrorView = Unauthorized - } - } - - if (routeSegments?.length === 3) { - // `../:slug/api`, `../:slug/preview`, `../:slug/versions`, etc - switch (nestedViewSlug) { - case 'api': { - if (globalConfig?.admin?.hideAPIURL !== true) { - CustomView = getCustomViewByKey(views, 'API') - DefaultView = DefaultAPIView - } - break - } - - case 'preview': { - if (livePreviewEnabled) { - DefaultView = DefaultLivePreviewView - } - break - } - - case 'versions': { - if (docPermissions?.readVersions?.permission) { - CustomView = getCustomViewByKey(views, 'Versions') - DefaultView = DefaultVersionsView - } else { - ErrorView = Unauthorized - } - break - } - - default: { - if (docPermissions?.read?.permission) { - CustomView = getCustomViewByKey(views, 'Default') - DefaultView = DefaultEditView - } else { - ErrorView = Unauthorized - } - break - } - } - } - - if (routeSegments?.length === 4) { - // `../:slug/versions/:version`, etc - if (nestedViewSlug === 'versions') { - if (docPermissions?.readVersions?.permission) { - CustomView = getCustomViewByKey(views, 'Version') - DefaultView = DefaultVersionView + switch (routeSegments.length) { + case 2: { + if (docPermissions?.read?.permission) { + CustomView = getCustomViewByKey(views, 'Default') + DefaultView = DefaultEditView } else { ErrorView = Unauthorized } + break + } + + case 3: { + // `../:slug/api`, `../:slug/preview`, `../:slug/versions`, etc + switch (segment3) { + case 'api': { + if (globalConfig?.admin?.hideAPIURL !== true) { + CustomView = getCustomViewByKey(views, 'API') + DefaultView = DefaultAPIView + } + break + } + + case 'preview': { + if (livePreviewEnabled) { + DefaultView = DefaultLivePreviewView + } + break + } + + case 'versions': { + if (docPermissions?.readVersions?.permission) { + CustomView = getCustomViewByKey(views, 'Versions') + DefaultView = DefaultVersionsView + } else { + ErrorView = Unauthorized + } + break + } + + default: { + if (docPermissions?.read?.permission) { + CustomView = getCustomViewByKey(views, 'Default') + DefaultView = DefaultEditView + } else { + ErrorView = Unauthorized + } + break + } + } + break + } + + default: { + // `../:slug/versions/:version`, etc + if (segment3 === 'versions') { + if (docPermissions?.readVersions?.permission) { + CustomView = getCustomViewByKey(views, 'Version') + DefaultView = DefaultVersionView + } else { + ErrorView = Unauthorized + } + } else { + const baseRoute = [adminRoute, 'globals', globalSlug].filter(Boolean).join('/') + + const currentRoute = [baseRoute, segment3, ...remainingSegments] + .filter(Boolean) + .join('/') + + CustomView = getCustomViewByRoute({ + baseRoute, + currentRoute, + views, + }) + + if (!CustomView) ErrorView = () => + } + break } } } diff --git a/packages/next/src/views/Document/index.tsx b/packages/next/src/views/Document/index.tsx index 670c49ca7..abcc9330a 100644 --- a/packages/next/src/views/Document/index.tsx +++ b/packages/next/src/views/Document/index.tsx @@ -1,23 +1,14 @@ import type { EditViewComponent } from 'payload/config' -import type { - AdminViewComponent, - DocumentPreferences, - Document as DocumentType, - Field, - ServerSideEditViewProps, -} from 'payload/types' +import type { AdminViewComponent, ServerSideEditViewProps } from 'payload/types' import type { DocumentPermissions } from 'payload/types' import type { AdminViewProps } from 'payload/types' import { DocumentHeader } from '@payloadcms/ui/elements/DocumentHeader' import { HydrateClientUser } from '@payloadcms/ui/elements/HydrateClientUser' import { RenderCustomComponent } from '@payloadcms/ui/elements/RenderCustomComponent' -import { buildStateFromSchema } from '@payloadcms/ui/forms/buildStateFromSchema' import { DocumentInfoProvider } from '@payloadcms/ui/providers/DocumentInfo' import { EditDepthProvider } from '@payloadcms/ui/providers/EditDepth' import { FormQueryParamsProvider } from '@payloadcms/ui/providers/FormQueryParams' -import { formatDocTitle } from '@payloadcms/ui/utilities/formatDocTitle' -import { formatFields } from '@payloadcms/ui/utilities/formatFields' import { docAccessOperation } from 'payload/operations' import React from 'react' @@ -222,6 +213,7 @@ export const Document: React.FC = async ({ const viewComponentProps: ServerSideEditViewProps = { initPageResult, + params, routeSegments: segments, searchParams, } diff --git a/packages/next/src/views/Root/getCustomViewByRoute.tsx b/packages/next/src/views/Root/getCustomViewByRoute.tsx index 425550282..6d8df2210 100644 --- a/packages/next/src/views/Root/getCustomViewByRoute.tsx +++ b/packages/next/src/views/Root/getCustomViewByRoute.tsx @@ -1,6 +1,6 @@ import type { AdminViewComponent, SanitizedConfig } from 'payload/types' -import { pathToRegexp } from 'path-to-regexp' +import { isPathMatchingRoute } from './isPathMatchingRoute.js' export const getCustomViewByRoute = ({ config, @@ -23,22 +23,13 @@ export const getCustomViewByRoute = ({ typeof views === 'object' && Object.entries(views).find(([, view]) => { if (typeof view === 'object') { - const { exact, path: viewPath, sensitive, strict } = view - - const keys = [] - - // run the view path through `pathToRegexp` to resolve any dynamic segments - // i.e. `/admin/custom-view/:id` -> `/admin/custom-view/123` - const regex = pathToRegexp(viewPath, keys, { - sensitive, - strict, + return isPathMatchingRoute({ + currentRoute, + exact: view.exact, + path: view.path, + sensitive: view.sensitive, + strict: view.strict, }) - - const match = regex.exec(currentRoute) - const viewRoute = match?.[0] || viewPath - - if (exact) return currentRoute === viewRoute - if (!exact) return viewRoute.startsWith(currentRoute) } })?.[1] diff --git a/packages/next/src/views/Root/isPathMatchingRoute.ts b/packages/next/src/views/Root/isPathMatchingRoute.ts new file mode 100644 index 000000000..4b03fb947 --- /dev/null +++ b/packages/next/src/views/Root/isPathMatchingRoute.ts @@ -0,0 +1,30 @@ +import { pathToRegexp } from 'path-to-regexp' + +export const isPathMatchingRoute = ({ + currentRoute, + exact, + path: viewPath, + sensitive, + strict, +}: { + currentRoute: string + exact?: boolean + path?: string + sensitive?: boolean + strict?: boolean +}) => { + const keys = [] + + // run the view path through `pathToRegexp` to resolve any dynamic segments + // i.e. `/admin/custom-view/:id` -> `/admin/custom-view/123` + const regex = pathToRegexp(viewPath, keys, { + sensitive, + strict, + }) + + const match = regex.exec(currentRoute) + const viewRoute = match?.[0] || viewPath + + if (exact) return currentRoute === viewRoute + if (!exact) return viewRoute.startsWith(currentRoute) +} diff --git a/packages/payload/src/admin/views/types.ts b/packages/payload/src/admin/views/types.ts index e57338792..9500532d1 100644 --- a/packages/payload/src/admin/views/types.ts +++ b/packages/payload/src/admin/views/types.ts @@ -42,6 +42,7 @@ export type InitPageResult = { export type ServerSideEditViewProps = { initPageResult: InitPageResult + params: { [key: string]: string | string[] | undefined } routeSegments: string[] searchParams: { [key: string]: string | string[] | undefined } } diff --git a/packages/ui/src/elements/DocumentHeader/Tabs/index.tsx b/packages/ui/src/elements/DocumentHeader/Tabs/index.tsx index d1dbc94df..4897aedf8 100644 --- a/packages/ui/src/elements/DocumentHeader/Tabs/index.tsx +++ b/packages/ui/src/elements/DocumentHeader/Tabs/index.tsx @@ -5,7 +5,7 @@ import type { SanitizedGlobalConfig, } from 'payload/types' -import { isPlainObject } from 'packages/payload/src/utilities/isPlainObject.js' +import { isPlainObject } from 'payload/utilities' import React from 'react' import { ShouldRenderTabs } from './ShouldRenderTabs.js' diff --git a/test/admin/collections/CustomViews2.ts b/test/admin/collections/CustomViews2.ts index 96e1e570e..a0c6a9f5a 100644 --- a/test/admin/collections/CustomViews2.ts +++ b/test/admin/collections/CustomViews2.ts @@ -1,11 +1,14 @@ import type { CollectionConfig } from 'payload/types' import { CustomTabComponent } from '../components/CustomTabComponent/index.js' -import { CustomTabView } from '../components/views/CustomTab/index.js' -import { CustomTabView2 } from '../components/views/CustomTab2/index.js' +import { CustomTabComponentView } from '../components/views/CustomTabComponent/index.js' +import { CustomTabLabelView } from '../components/views/CustomTabLabel/index.js' import { CustomNestedTabView } from '../components/views/CustomTabNested/index.js' +import { CustomTabWithParamView } from '../components/views/CustomTabWithParam/index.js' import { CustomVersionsView } from '../components/views/CustomVersions/index.js' import { + customCollectionParamViewPath, + customCollectionParamViewPathBase, customEditLabel, customNestedTabViewPath, customTabLabel, @@ -26,7 +29,7 @@ export const CustomViews2: CollectionConfig = { }, }, MyCustomView: { - Component: CustomTabView, + Component: CustomTabLabelView, Tab: { href: '/custom-tab-view', label: customTabLabel, @@ -34,15 +37,27 @@ export const CustomViews2: CollectionConfig = { path: '/custom-tab-view', }, MyCustomViewWithCustomTab: { - Component: CustomTabView2, + Component: CustomTabComponentView, Tab: CustomTabComponent, path: customTabViewPath, }, MyCustomViewWithNestedPath: { Component: CustomNestedTabView, path: customNestedTabViewPath, + tab: { + label: 'Custom Nested Tab View', + href: customNestedTabViewPath, + }, }, Versions: CustomVersionsView, + CustomViewWithParam: { + Component: CustomTabWithParamView, + path: customCollectionParamViewPath, + Tab: { + label: 'Custom Param View', + href: `${customCollectionParamViewPathBase}/123`, + }, + }, }, }, }, diff --git a/test/admin/components/CustomTabComponent/client.tsx b/test/admin/components/CustomTabComponent/client.tsx index 30f39fc10..dbaaa99d9 100644 --- a/test/admin/components/CustomTabComponent/client.tsx +++ b/test/admin/components/CustomTabComponent/client.tsx @@ -1,16 +1,21 @@ 'use client' -import LinkImport from 'next/link' -import { usePathname } from 'next/navigation' +import { useConfig } from '@payloadcms/ui/providers/Config' +import LinkImport from 'next/link.js' +import { useParams } from 'next/navigation' import React from 'react' const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default export const CustomTabComponentClient: React.FC<{ path: string -}> = (props) => { - const { path } = props - const pathname = usePathname() +}> = ({ path }) => { + const { + routes: { admin: adminRoute }, + } = useConfig() + const params = useParams() - return Custom Tab Component + const baseRoute = (params.segments.slice(0, 3) as string[]).join('/') + + return Custom Tab Component } diff --git a/test/admin/components/views/CustomDefault/index.scss b/test/admin/components/views/CustomDefault/index.scss index d5b137700..fdadc4af9 100644 --- a/test/admin/components/views/CustomDefault/index.scss +++ b/test/admin/components/views/CustomDefault/index.scss @@ -1,5 +1,5 @@ // As this is the demo folder, we import Payload SCSS functions relatively. -@import '../../../../../packages/ui/src/exports/scss.scss'; +@import '../../../../../packages/ui/src/scss/styles.scss'; // In your own projects, you'd import as follows: // @import '~payload/scss'; diff --git a/test/admin/components/views/CustomMinimal/index.scss b/test/admin/components/views/CustomMinimal/index.scss index 8d07764f4..82fccdd38 100644 --- a/test/admin/components/views/CustomMinimal/index.scss +++ b/test/admin/components/views/CustomMinimal/index.scss @@ -1,5 +1,5 @@ // As this is the demo folder, we import Payload SCSS functions relatively. -@import '../../../../../packages/ui/src/exports/scss.scss'; +@import '../../../../../packages/ui/src/scss/styles.scss'; // In your own projects, you'd import as follows: // @import '~payload/scss'; diff --git a/test/admin/components/views/CustomTab2/index.tsx b/test/admin/components/views/CustomTabComponent/index.tsx similarity index 77% rename from test/admin/components/views/CustomTab2/index.tsx rename to test/admin/components/views/CustomTabComponent/index.tsx index 51a7457ae..e17d617bd 100644 --- a/test/admin/components/views/CustomTab2/index.tsx +++ b/test/admin/components/views/CustomTabComponent/index.tsx @@ -4,9 +4,9 @@ import React, { Fragment } from 'react' import type { ServerSideEditViewProps } from '../../../../../packages/payload/types.js' -import { customTabViewTitle } from '../../../shared.js' +import { customTabViewComponentTitle } from '../../../shared.js' -export const CustomTabView2: React.FC = ({ initPageResult }) => { +export const CustomTabComponentView: React.FC = ({ initPageResult }) => { if (!initPageResult) { notFound() } @@ -27,7 +27,7 @@ export const CustomTabView2: React.FC = ({ initPageResu paddingRight: 'var(--gutter-h)', }} > -

{customTabViewTitle}

+

{customTabViewComponentTitle}

This custom view was added through the Payload config:

  • diff --git a/test/admin/components/views/CustomTab/index.tsx b/test/admin/components/views/CustomTabLabel/index.tsx similarity index 78% rename from test/admin/components/views/CustomTab/index.tsx rename to test/admin/components/views/CustomTabLabel/index.tsx index e220c27b7..85f4e6ce5 100644 --- a/test/admin/components/views/CustomTab/index.tsx +++ b/test/admin/components/views/CustomTabLabel/index.tsx @@ -4,9 +4,9 @@ import React, { Fragment } from 'react' import type { ServerSideEditViewProps } from '../../../../../packages/payload/types.js' -import { customTabViewTitle } from '../../../shared.js' +import { customTabLabelViewTitle } from '../../../shared.js' -export const CustomTabView: React.FC = ({ initPageResult }) => { +export const CustomTabLabelView: React.FC = ({ initPageResult }) => { if (!initPageResult) { notFound() } @@ -27,7 +27,7 @@ export const CustomTabView: React.FC = ({ initPageResul paddingRight: 'var(--gutter-h)', }} > -

    {customTabViewTitle}

    +

    {customTabLabelViewTitle}

    This custom view was added through the Payload config:

    • diff --git a/test/admin/components/views/CustomTabWithParam/index.tsx b/test/admin/components/views/CustomTabWithParam/index.tsx new file mode 100644 index 000000000..4b451530a --- /dev/null +++ b/test/admin/components/views/CustomTabWithParam/index.tsx @@ -0,0 +1,24 @@ +import type { AdminViewProps } from 'payload/types.js' + +import React from 'react' + +import { customParamViewTitle } from '../../../shared.js' + +export const CustomTabWithParamView: React.FC = ({ params }) => { + const paramValue = params?.segments?.[4] + + return ( +
      +

      {customParamViewTitle}

      +

      + This custom collection view is using a dynamic URL parameter `slug: {paramValue || 'None'}` +

      +
      + ) +} diff --git a/test/admin/globals/CustomViews2.ts b/test/admin/globals/CustomViews2.ts index 373dc7b4e..350665111 100644 --- a/test/admin/globals/CustomViews2.ts +++ b/test/admin/globals/CustomViews2.ts @@ -2,7 +2,8 @@ import type { GlobalConfig } from 'payload/types' import { CustomTabComponent } from '../components/CustomTabComponent/index.js' import { CustomDefaultEditView } from '../components/views/CustomEditDefault/index.js' -import { CustomTabView } from '../components/views/CustomTab/index.js' +import { CustomTabComponentView } from '../components/views/CustomTabComponent/index.js' +import { CustomTabLabelView } from '../components/views/CustomTabLabel/index.js' import { CustomVersionsView } from '../components/views/CustomVersions/index.js' import { customGlobalViews2GlobalSlug } from '../slugs.js' @@ -14,7 +15,7 @@ export const CustomGlobalViews2: GlobalConfig = { Edit: { Default: CustomDefaultEditView, MyCustomView: { - Component: CustomTabView, + Component: CustomTabLabelView, Tab: { href: '/custom-tab-view', label: 'Custom', @@ -22,7 +23,7 @@ export const CustomGlobalViews2: GlobalConfig = { path: '/custom-tab-view', }, MyCustomViewWithCustomTab: { - Component: CustomTabView, + Component: CustomTabComponentView, Tab: CustomTabComponent, path: '/custom-tab-component', }, diff --git a/test/admin/shared.ts b/test/admin/shared.ts index 09aa0d523..24668cdb2 100644 --- a/test/admin/shared.ts +++ b/test/admin/shared.ts @@ -22,8 +22,14 @@ export const customTabLabel = 'Custom Tab Label' export const customTabViewPath = '/custom-tab-component' -export const customTabViewTitle = 'Custom View With Tab Component' +export const customTabLabelViewTitle = 'Custom Tab Label View' + +export const customTabViewComponentTitle = 'Custom View With Tab Component' export const customNestedTabViewPath = `${customTabViewPath}/nested-view` export const customNestedTabViewTitle = 'Custom Nested Tab View' + +export const customCollectionParamViewPathBase = '/custom-param' + +export const customCollectionParamViewPath = `${customCollectionParamViewPathBase}/:slug` diff --git a/tsconfig.json b/tsconfig.json index 706245795..645fbaeff 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -28,7 +28,7 @@ } ], "paths": { - "@payload-config": ["./test/_community/config.ts"], + "@payload-config": ["./test/admin/config.ts"], "@payloadcms/live-preview": ["./packages/live-preview/src"], "@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"], "@payloadcms/ui/assets": ["./packages/ui/src/assets/index.ts"],