diff --git a/docs/configuration/database.mdx b/docs/configuration/database.mdx deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/fields/point.mdx b/docs/fields/point.mdx index ebfc80ec2c..60219ab7c9 100644 --- a/docs/fields/point.mdx +++ b/docs/fields/point.mdx @@ -12,6 +12,10 @@ keywords: point, geolocation, geospatial, geojson, 2dsphere, config, configurati related queries. + + Note: The Point field type is currently only supported in MongoDB. + + { const config = useConfig() const { - admin: { - components: { - views: { Account: CustomAccount } = { - Account: undefined, - }, - } = {}, - }, + admin: { components: { views: { Account: CustomAccountComponent } = {} } = {} }, collections, routes: { api }, serverURL, @@ -133,7 +127,9 @@ const AccountView: React.FC = () => { return ( { const [filteredGlobals, setFilteredGlobals] = useState([]) const { - admin: { - components: { - views: { Dashboard: CustomDashboard } = { - Dashboard: undefined, - }, - } = {}, - } = {}, + admin: { components: { views: { Dashboard: CustomDashboardComponent } = {} } = {} } = {}, collections, globals, } = useConfig() @@ -37,7 +31,9 @@ const Dashboard: React.FC = () => { return ( { - const { canAccessAdmin, customRoutes, match, user } = props + const { canAccessAdmin, config, match, user } = props + + const { + admin: { components: { views: customViewConfigs } = {} }, + } = config + + const customViews = Object.entries(customViewConfigs || {}) + .filter(([viewKey, view]) => { + // Remove internal views from the list of custom views + // This way we can easily iterate over the remaining views + return Boolean( + !internalViews.includes(viewKey as any) && + typeof view !== 'function' && + typeof view === 'object', + ) + }) + ?.map(([, view]) => view) + + if (Array.isArray(customViews)) { + return customViews.map((CustomView) => { + // You are responsible for ensuring that your own custom route is secure + // i.e. return `Unauthorized` in your own component if the user does not have permission + + if (typeof CustomView === 'function') { + return + } + + const { Component, exact, path, sensitive, strict } = CustomView - if (Array.isArray(customRoutes)) { - return customRoutes.map(({ Component, exact, path, sensitive, strict }) => { return ( - // You are responsible for ensuring that your own custom route is secure - // i.e. return `Unauthorized` in your own component if the user does not have permission { const config = useConfig() const { - admin: { - components: { routes: customRoutesConfig } = {}, - inactivityRoute: logoutInactivityRoute, - logoutRoute, - user: userSlug, - }, + admin: { inactivityRoute: logoutInactivityRoute, logoutRoute, user: userSlug }, collections, globals, routes, @@ -110,7 +105,7 @@ export const Routes: React.FC = () => { {customRoutes({ canAccessAdmin, - customRoutes: customRoutesConfig, + config, match, user, })} diff --git a/packages/payload/src/config/schema.ts b/packages/payload/src/config/schema.ts index d35376a47b..40669750ab 100644 --- a/packages/payload/src/config/schema.ts +++ b/packages/payload/src/config/schema.ts @@ -1,7 +1,7 @@ import joi from 'joi' +import { adminViewSchema } from './shared/adminViewSchema' import { livePreviewSchema } from './shared/componentSchema' -import { routeSchema } from './shared/routeSchema' const component = joi.alternatives().try(joi.object().unknown(), joi.func()) @@ -53,11 +53,13 @@ export default joi.object({ Button: component, }), providers: joi.array().items(component), - routes: routeSchema, - views: joi.object({ - Account: component, - Dashboard: component, - }), + views: joi.alternatives().try( + joi.object({ + Account: joi.alternatives().try(component, adminViewSchema), + Dashboard: joi.alternatives().try(component, adminViewSchema), + }), + joi.object().pattern(joi.string(), component), + ), }), css: joi.string(), dateFormat: joi.string(), diff --git a/packages/payload/src/config/shared/routeSchema.ts b/packages/payload/src/config/shared/adminViewSchema.ts similarity index 83% rename from packages/payload/src/config/shared/routeSchema.ts rename to packages/payload/src/config/shared/adminViewSchema.ts index 6f6621bb29..a38205bb16 100644 --- a/packages/payload/src/config/shared/routeSchema.ts +++ b/packages/payload/src/config/shared/adminViewSchema.ts @@ -2,7 +2,7 @@ import joi from 'joi' import { componentSchema } from './componentSchema' -export const routeSchema = joi.array().items( +export const adminViewSchema = joi.array().items( joi.object().keys({ Component: componentSchema, exact: joi.bool(), diff --git a/packages/payload/src/config/types.ts b/packages/payload/src/config/types.ts index 2288700e20..4b4a90ab98 100644 --- a/packages/payload/src/config/types.ts +++ b/packages/payload/src/config/types.ts @@ -232,12 +232,25 @@ export type Endpoint = { root?: boolean } -export type CustomAdminView = React.ComponentType<{ +export type AdminViewConfig = { + Component: AdminViewComponent + /** Whether the path should be matched exactly or as a prefix */ + exact?: boolean + path: string + sensitive?: boolean + strict?: boolean +} + +export type AdminViewProps = { canAccessAdmin?: boolean collection?: SanitizedCollectionConfig global?: SanitizedGlobalConfig user: User | null | undefined -}> +} + +export type AdminViewComponent = React.ComponentType + +export type AdminView = AdminViewComponent | AdminViewConfig export type EditViewConfig = { /** @@ -257,15 +270,6 @@ export type EditViewComponent = React.ComponentType<{ export type EditView = EditViewComponent | EditViewConfig -export type AdminRoute = { - Component: CustomAdminView - /** Whether the path should be matched exactly or as a prefix */ - exact?: boolean - path: string - sensitive?: boolean - strict?: boolean -} - export type Locale = { /** * value of supported locale @@ -422,15 +426,18 @@ export type Config = { */ providers?: React.ComponentType<{ children: React.ReactNode }>[] /** - * Add custom routes in the admin dashboard + * Replace or modify top-level admin routes, or add new ones: + * + `Account` - `/admin/account` + * + `Dashboard` - `/admin` + * + `:path` - `/admin/:path` */ - routes?: AdminRoute[] - /* Replace complete pages */ views?: { + /** Add custom admin views */ + [key: string]: AdminView /** Replace the account screen */ - Account?: React.ComponentType + Account?: AdminView /** Replace the admin homepage */ - Dashboard?: React.ComponentType + Dashboard?: AdminView } } /** Absolute path to a stylesheet that you can use to override / customize the Admin panel styling. */ diff --git a/test/admin/components/AfterNavLinks/index.tsx b/test/admin/components/AfterNavLinks/index.tsx index 38b1616d86..403de3cb74 100644 --- a/test/admin/components/AfterNavLinks/index.tsx +++ b/test/admin/components/AfterNavLinks/index.tsx @@ -30,7 +30,7 @@ const AfterNavLinks: React.FC = () => { Default Template @@ -39,7 +39,7 @@ const AfterNavLinks: React.FC = () => { Minimal Template diff --git a/test/admin/components/routes/CustomDefault/index.tsx b/test/admin/components/routes/CustomDefault/index.tsx deleted file mode 100644 index cd5f3c969c..0000000000 --- a/test/admin/components/routes/CustomDefault/index.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React, { useEffect } from 'react' -import { Redirect } from 'react-router-dom' - -import type { CustomAdminView } from '../../../../../packages/payload/src/config/types' - -import Button from '../../../../../packages/payload/src/admin/components/elements/Button' -import { useStepNav } from '../../../../../packages/payload/src/admin/components/elements/StepNav' -// As this is the demo project, we import our dependencies from the `src` directory. -import DefaultTemplate from '../../../../../packages/payload/src/admin/components/templates/Default' -import { useConfig } from '../../../../../packages/payload/src/admin/components/utilities/Config' -import Meta from '../../../../../packages/payload/src/admin/components/utilities/Meta' - -// In your projects, you can import as follows: -// import { DefaultTemplate } from 'payload/components/templates'; -// import { Button, Eyebrow } from 'payload/components/elements'; -// import { AdminView } from 'payload/config'; -// import { useStepNav } from 'payload/components/hooks'; -// import { useConfig, Meta } from 'payload/components/utilities'; - -const CustomDefaultRoute: CustomAdminView = ({ canAccessAdmin, user }) => { - const { - routes: { admin: adminRoute }, - } = useConfig() - const { setStepNav } = useStepNav() - - // This effect will only run one time and will allow us - // to set the step nav to display our custom route name - - useEffect(() => { - setStepNav([ - { - label: 'Custom Route with Default Template', - }, - ]) - }, [setStepNav]) - - // If an unauthorized user tries to navigate straight to this page, - // Boot 'em out - if (!user || (user && !canAccessAdmin)) { - return - } - - return ( - - -
-

Custom Route

-

- Here is a custom route that was added in the Payload config. It uses the Default Template, - so the sidebar is rendered. -

- -
-
- ) -} - -export default CustomDefaultRoute diff --git a/test/admin/components/views/CustomAccount/index.tsx b/test/admin/components/views/CustomAccount/index.tsx new file mode 100644 index 0000000000..36269558c2 --- /dev/null +++ b/test/admin/components/views/CustomAccount/index.tsx @@ -0,0 +1,27 @@ +import React, { Fragment } from 'react' + +import type { AdminViewComponent } from '../../../../../packages/payload/src/config/types' + +const CustomAccountView: AdminViewComponent = () => { + return ( + +
+

Custom Account View

+

This custom view was added through the Payload config:

+
    +
  • + components.views.Account +
  • +
+
+
+ ) +} + +export default CustomAccountView diff --git a/test/admin/components/views/CustomDashboard/index.tsx b/test/admin/components/views/CustomDashboard/index.tsx new file mode 100644 index 0000000000..fe1f3bdae7 --- /dev/null +++ b/test/admin/components/views/CustomDashboard/index.tsx @@ -0,0 +1,27 @@ +import React, { Fragment } from 'react' + +import { AdminViewComponent } from '../../../../../packages/payload/src/config/types' + +const CustomDashboardView: AdminViewComponent = () => { + return ( + +
+

Custom Dashboard View

+

This custom view was added through the Payload config:

+
    +
  • + components.views.Dashboard +
  • +
+
+
+ ) +} + +export default CustomDashboardView diff --git a/test/admin/components/routes/CustomMinimal/index.scss b/test/admin/components/views/CustomDefault/index.scss similarity index 52% rename from test/admin/components/routes/CustomMinimal/index.scss rename to test/admin/components/views/CustomDefault/index.scss index 159e1d92c4..87c90d5a5d 100644 --- a/test/admin/components/routes/CustomMinimal/index.scss +++ b/test/admin/components/views/CustomDefault/index.scss @@ -4,8 +4,27 @@ // In your own projects, you'd import as follows: // @import '~payload/scss'; -.custom-minimal-route { +.custom-default-view { &__login-btn { margin-right: base(0.5); } + + &__content { + display: flex; + flex-direction: column; + gap: base(1); + + & > * { + margin: 0; + } + } + + &__controls { + display: flex; + gap: calc(var(--base) / 2); + + & > * { + margin: 0; + } + } } diff --git a/test/admin/components/views/CustomDefault/index.tsx b/test/admin/components/views/CustomDefault/index.tsx index f210cfb923..5302bc47f0 100644 --- a/test/admin/components/views/CustomDefault/index.tsx +++ b/test/admin/components/views/CustomDefault/index.tsx @@ -1,21 +1,30 @@ -import React, { Fragment, useEffect } from 'react' +import React, { useEffect } from 'react' import { Redirect } from 'react-router-dom' -import type { EditViewComponent } from '../../../../../packages/payload/src/config/types' +import type { AdminViewComponent } from '../../../../../packages/payload/src/config/types' +import Button from '../../../../../packages/payload/src/admin/components/elements/Button' import { useStepNav } from '../../../../../packages/payload/src/admin/components/elements/StepNav' +// As this is the demo project, we import our dependencies from the `src` directory. +import DefaultTemplate from '../../../../../packages/payload/src/admin/components/templates/Default' import { useConfig } from '../../../../../packages/payload/src/admin/components/utilities/Config' +import Meta from '../../../../../packages/payload/src/admin/components/utilities/Meta' -const CustomDefaultView: EditViewComponent = ({ - canAccessAdmin, - // collection, - // global, - user, -}) => { +// In your projects, you can import as follows: +// import { DefaultTemplate } from 'payload/components/templates'; +// import { Button, Eyebrow } from 'payload/components/elements'; +// import { AdminView } from 'payload/config'; +// import { useStepNav } from 'payload/components/hooks'; +// import { useConfig, Meta } from 'payload/components/utilities'; + +import './index.scss' + +const baseClass = 'custom-default-view' + +const CustomDefaultView: AdminViewComponent = ({ canAccessAdmin, user }) => { const { routes: { admin: adminRoute }, } = useConfig() - const { setStepNav } = useStepNav() // This effect will only run one time and will allow us @@ -24,7 +33,7 @@ const CustomDefaultView: EditViewComponent = ({ useEffect(() => { setStepNav([ { - label: 'Custom Default View', + label: 'Custom Admin View with Default Template', }, ]) }, [setStepNav]) @@ -36,39 +45,31 @@ const CustomDefaultView: EditViewComponent = ({ } return ( - + +
-

Custom Default View

-

This custom Default view was added through one of the following Payload configs:

-
    -
  • - components.views.Edit.Default -

    - {'This allows you to override only the default edit view specifically, but '} - - not - - { - ' any nested views like versions, etc. The document header will render above this component.' - } -

    -
  • -
  • - components.views.Edit.Default.Component -

    - This is the most granular override, allowing you to override only the Default - component, or any of its other properties like path and label. -

    -
  • -
+

Custom Admin View

+

+ Here is a custom admin view that was added in the Payload config. It uses the Default + Template, so the sidebar is rendered. +

+
+ +
-
+ ) } diff --git a/test/admin/components/views/CustomDefaultEdit/index.tsx b/test/admin/components/views/CustomDefaultEdit/index.tsx new file mode 100644 index 0000000000..f210cfb923 --- /dev/null +++ b/test/admin/components/views/CustomDefaultEdit/index.tsx @@ -0,0 +1,75 @@ +import React, { Fragment, useEffect } from 'react' +import { Redirect } from 'react-router-dom' + +import type { EditViewComponent } from '../../../../../packages/payload/src/config/types' + +import { useStepNav } from '../../../../../packages/payload/src/admin/components/elements/StepNav' +import { useConfig } from '../../../../../packages/payload/src/admin/components/utilities/Config' + +const CustomDefaultView: EditViewComponent = ({ + canAccessAdmin, + // collection, + // global, + user, +}) => { + const { + routes: { admin: adminRoute }, + } = useConfig() + + const { setStepNav } = useStepNav() + + // This effect will only run one time and will allow us + // to set the step nav to display our custom route name + + useEffect(() => { + setStepNav([ + { + label: 'Custom Default View', + }, + ]) + }, [setStepNav]) + + // If an unauthorized user tries to navigate straight to this page, + // Boot 'em out + if (!user || (user && !canAccessAdmin)) { + return + } + + return ( + +
+

Custom Default View

+

This custom Default view was added through one of the following Payload configs:

+
    +
  • + components.views.Edit.Default +

    + {'This allows you to override only the default edit view specifically, but '} + + not + + { + ' any nested views like versions, etc. The document header will render above this component.' + } +

    +
  • +
  • + components.views.Edit.Default.Component +

    + This is the most granular override, allowing you to override only the Default + component, or any of its other properties like path and label. +

    +
  • +
+
+
+ ) +} + +export default CustomDefaultView diff --git a/test/admin/components/views/CustomMinimal/index.scss b/test/admin/components/views/CustomMinimal/index.scss new file mode 100644 index 0000000000..3a52cd57f8 --- /dev/null +++ b/test/admin/components/views/CustomMinimal/index.scss @@ -0,0 +1,30 @@ +// As this is the demo folder, we import Payload SCSS functions relatively. +@import '../../../../../packages/payload/src/exports/scss.scss'; + +// In your own projects, you'd import as follows: +// @import '~payload/scss'; + +.custom-minimal-view { + &__login-btn { + margin-right: base(0.5); + } + + &__content { + display: flex; + flex-direction: column; + gap: base(1); + + & > * { + margin: 0; + } + } + + &__controls { + display: flex; + gap: calc(var(--base) / 2); + + & > * { + margin: 0; + } + } +} diff --git a/test/admin/components/routes/CustomMinimal/index.tsx b/test/admin/components/views/CustomMinimal/index.tsx similarity index 56% rename from test/admin/components/routes/CustomMinimal/index.tsx rename to test/admin/components/views/CustomMinimal/index.tsx index 6a186c6016..f265d4d2e4 100644 --- a/test/admin/components/routes/CustomMinimal/index.tsx +++ b/test/admin/components/views/CustomMinimal/index.tsx @@ -12,25 +12,29 @@ import { useConfig } from '../../../../../packages/payload/src/admin/components/ import './index.scss' -const baseClass = 'custom-minimal-route' +const baseClass = 'custom-minimal-view' -const CustomMinimalRoute: React.FC = () => { +const CustomMinimalView: React.FC = () => { const { routes: { admin: adminRoute }, } = useConfig() return ( -

Custom Route

-

Here is a custom route that was added in the Payload config.

- - +
+

Custom Admin View

+

Here is a custom admin view that was added in the Payload config.

+
+ + +
+
) } -export default CustomMinimalRoute +export default CustomMinimalView diff --git a/test/admin/config.ts b/test/admin/config.ts index 1f90e1b524..1759124fe4 100644 --- a/test/admin/config.ts +++ b/test/admin/config.ts @@ -11,10 +11,10 @@ import CustomTabComponent from './components/CustomTabComponent' import DemoUIFieldCell from './components/DemoUIField/Cell' import DemoUIFieldField from './components/DemoUIField/Field' import Logout from './components/Logout' -import CustomDefaultRoute from './components/routes/CustomDefault' -import CustomMinimalRoute from './components/routes/CustomMinimal' import CustomDefaultView from './components/views/CustomDefault' +import CustomDefaultEditView from './components/views/CustomDefaultEdit' import CustomEditView from './components/views/CustomEdit' +import CustomMinimalRoute from './components/views/CustomMinimal' import CustomVersionsView from './components/views/CustomVersions' import CustomView from './components/views/CustomView' import { globalSlug, postsSlug, slugPluralLabel, slugSingularLabel } from './shared' @@ -32,16 +32,6 @@ export default buildConfigWithDefaults({ css: path.resolve(__dirname, 'styles.scss'), components: { // providers: [CustomProvider, CustomProvider], - routes: [ - { - path: '/custom-minimal-route', - Component: CustomMinimalRoute, - }, - { - path: '/custom-default-route', - Component: CustomDefaultRoute, - }, - ], afterDashboard: [AfterDashboard], beforeLogin: [BeforeLogin], logout: { @@ -51,6 +41,14 @@ export default buildConfigWithDefaults({ views: { // Dashboard: CustomDashboardView, // Account: CustomAccountView, + CustomMinimalRoute: { + path: '/custom-minimal-view', + Component: CustomMinimalRoute, + }, + CustomDefaultRoute: { + path: '/custom-default-view', + Component: CustomDefaultView, + }, }, }, }, @@ -184,7 +182,7 @@ export default buildConfigWithDefaults({ views: { Edit: { // This will override one specific nested view within the `/edit/:id` route, i.e. `/edit/:id/versions` - Default: CustomDefaultView, + Default: CustomDefaultEditView, Versions: CustomVersionsView, MyCustomView: { path: '/custom-tab-view', @@ -328,7 +326,7 @@ export default buildConfigWithDefaults({ components: { views: { Edit: { - Default: CustomDefaultView, + Default: CustomDefaultEditView, Versions: CustomVersionsView, MyCustomView: { path: '/custom-tab-view',