Skip to content

Commit 863abc0

Browse files
authored
feat(next): root admin (#7276)
1 parent b9cf6c7 commit 863abc0

File tree

72 files changed

+2703
-150
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+2703
-150
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ jobs:
292292
- access-control
293293
- admin__e2e__1
294294
- admin__e2e__2
295+
- admin-root
295296
- auth
296297
- field-error-states
297298
- fields-relationship

docs/admin/overview.mdx

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -169,19 +169,32 @@ The following options are available:
169169

170170
| Option | Default route | Description |
171171
| ------------------ | ----------------------- | ------------------------------------- |
172-
| `admin` | `/admin` | The Admin Panel itself. |
172+
| `admin` | `/admin` | The Admin Panel itself. |
173173
| `api` | `/api` | The [REST API](../rest-api/overview) base path. |
174174
| `graphQL` | `/graphql` | The [GraphQL API](../graphql/overview) base path. |
175175
| `graphQLPlayground`| `/graphql-playground` | The GraphQL Playground. |
176176

177-
<Banner type="warning">
178-
<strong>Warning:</strong>
179-
Changing Root-level Routes _after_ your project was generated will also require you to manually update the corresponding directories in your project. For example, changing `routes.admin` will require you to rename the `(payload)/admin` directory in your project to match the new route. [More details](#project-structure).
180-
</Banner>
181-
182177
<Banner type="success">
183178
<strong>Tip:</strong>
184-
You can easily add _new_ routes to the Admin Panel through the `endpoints` property of the Payload Config. See [Custom Endpoints](../rest-api/overview#custom-endpoints) for more information.
179+
You can easily add _new_ routes to the Admin Panel through [Custom Endpoints](../rest-api/overview#custom-endpoints) and [Custom Views](./views).
180+
</Banner>
181+
182+
#### Customizing Root-level Routes
183+
184+
You can change the Root-level Routes as needed, such as to mount the Admin Panel at the root of your application.
185+
186+
Changing Root-level Routes also requires a change to [Project Structure](#project-structure) to match the new route. For example, if you set `routes.admin` to `/`, you would need to completely remove the `admin` directory from the project structure:
187+
188+
```plaintext
189+
app/
190+
├─ (payload)/
191+
├── [[...segments]]/
192+
├──── ...
193+
```
194+
195+
<Banner type="warning">
196+
<strong>Note:</strong>
197+
If you set Root-level Routes _before_ auto-generating the Admin Panel, your [Project Structure](#project-structure) will already be set up correctly.
185198
</Banner>
186199

187200
### Admin-level Routes

docs/fields/checkbox.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ desc: Checkbox field types allow the developer to save a boolean value in the da
66
keywords: checkbox, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
77
---
88

9-
The Checkbox Field type saves a boolean in the database.
9+
The Checkbox Field saves a boolean in the database.
1010

1111
<LightDarkImage
1212
srcLight="https://payloadcms.com/images/docs/fields/checkbox.png"

docs/fields/collapsible.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ desc: With the Collapsible field, you can place fields within a collapsible layo
66
keywords: row, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
77
---
88

9-
The Collapsible field is presentational-only and only affects the Admin Panel. By using it, you can place fields within a nice layout component that can be collapsed / expanded.
9+
The Collapsible Field is presentational-only and only affects the Admin Panel. By using it, you can place fields within a nice layout component that can be collapsed / expanded.
1010

1111
<LightDarkImage
1212
srcLight="https://payloadcms.com/images/docs/fields/collapsible.png"

docs/fields/relationship.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ desc: The Relationship field provides the ability to relate documents together.
66
keywords: relationship, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
77
---
88

9-
The Relationship field is one of the most powerful fields Payload features. It provides for the ability to easily relate documents together.
9+
The Relationship Field is one of the most powerful fields Payload features. It provides for the ability to easily relate documents together.
1010

1111
<LightDarkImage
1212
srcLight="https://payloadcms.com/images/docs/fields/relationship.png"

packages/next/src/elements/DocumentHeader/Tabs/Tab/TabLink.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import type { SanitizedConfig } from 'payload'
33

44
import { useSearchParams } from '@payloadcms/ui'
5+
import { formatAdminURL } from '@payloadcms/ui/shared'
56
import LinkImport from 'next/link.js'
67
import { useParams, usePathname } from 'next/navigation.js'
78
import React from 'react'
@@ -38,7 +39,12 @@ export const DocumentTabLink: React.FC<{
3839

3940
const [entityType, entitySlug, segmentThree, segmentFour, ...rest] = params.segments || []
4041
const isCollection = entityType === 'collections'
41-
let docPath = `${adminRoute}/${isCollection ? 'collections' : 'globals'}/${entitySlug}`
42+
43+
let docPath = formatAdminURL({
44+
adminRoute,
45+
path: `/${isCollection ? 'collections' : 'globals'}/${entitySlug}`,
46+
})
47+
4248
if (isCollection && segmentThree) {
4349
// doc ID
4450
docPath += `/${segmentThree}`

packages/next/src/elements/Nav/index.client.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
useNav,
1313
useTranslation,
1414
} from '@payloadcms/ui'
15-
import { EntityType, groupNavItems } from '@payloadcms/ui/shared'
15+
import { EntityType, formatAdminURL, groupNavItems } from '@payloadcms/ui/shared'
1616
import LinkWithDefault from 'next/link.js'
1717
import React, { Fragment } from 'react'
1818

@@ -25,7 +25,7 @@ export const DefaultNavClient: React.FC = () => {
2525
const {
2626
collections,
2727
globals,
28-
routes: { admin },
28+
routes: { admin: adminRoute },
2929
} = useConfig()
3030

3131
const { i18n } = useTranslation()
@@ -69,13 +69,13 @@ export const DefaultNavClient: React.FC = () => {
6969
let id: string
7070

7171
if (type === EntityType.collection) {
72-
href = `${admin}/collections/${entity.slug}`
72+
href = formatAdminURL({ adminRoute, path: `/collections/${entity.slug}` })
7373
entityLabel = getTranslation(entity.labels.plural, i18n)
7474
id = `nav-${entity.slug}`
7575
}
7676

7777
if (type === EntityType.global) {
78-
href = `${admin}/globals/${entity.slug}`
78+
href = formatAdminURL({ adminRoute, path: `/globals/${entity.slug}` })
7979
entityLabel = getTranslation(entity.label, i18n)
8080
id = `nav-global-${entity.slug}`
8181
}

packages/next/src/utilities/initPage/handleAdminPage.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ export const handleAdminPage = ({
2121
route: string
2222
}) => {
2323
if (isAdminRoute(route, adminRoute)) {
24-
const routeSegments = route.replace(adminRoute, '').split('/').filter(Boolean)
24+
const baseAdminRoute = adminRoute && adminRoute !== '/' ? route.replace(adminRoute, '') : route
25+
const routeSegments = baseAdminRoute.split('/').filter(Boolean)
2526
const [entityType, entitySlug, createOrID] = routeSegments
2627
const collectionSlug = entityType === 'collections' ? entitySlug : undefined
2728
const globalSlug = entityType === 'globals' ? entitySlug : undefined
@@ -56,4 +57,6 @@ export const handleAdminPage = ({
5657
globalConfig,
5758
}
5859
}
60+
61+
return {}
5962
}

packages/next/src/utilities/initPage/handleAuthRedirect.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { formatAdminURL } from '@payloadcms/ui/shared'
12
import { redirect } from 'next/navigation.js'
23
import * as qs from 'qs-esm'
34

@@ -30,7 +31,7 @@ export const handleAuthRedirect = ({
3031
: undefined,
3132
)
3233

33-
const adminLoginRoute = `${adminRoute}${loginRouteFromConfig}`
34+
const adminLoginRoute = formatAdminURL({ adminRoute, path: loginRouteFromConfig })
3435

3536
const customLoginRoute =
3637
typeof redirectUnauthenticatedUser === 'string' ? redirectUnauthenticatedUser : undefined

packages/next/src/views/Dashboard/Default/index.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Permissions, ServerProps, VisibleEntities } from 'payload'
33

44
import { getTranslation } from '@payloadcms/translations'
55
import { Button, Card, Gutter, SetStepNav, SetViewActions } from '@payloadcms/ui'
6-
import { EntityType, WithServerSideProps } from '@payloadcms/ui/shared'
6+
import { EntityType, WithServerSideProps, formatAdminURL } from '@payloadcms/ui/shared'
77
import React, { Fragment } from 'react'
88

99
import './index.scss'
@@ -100,19 +100,31 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
100100

101101
if (type === EntityType.collection) {
102102
title = getTranslation(entity.labels.plural, i18n)
103+
103104
buttonAriaLabel = t('general:showAllLabel', { label: title })
104-
href = `${adminRoute}/collections/${entity.slug}`
105-
createHREF = `${adminRoute}/collections/${entity.slug}/create`
105+
106+
href = formatAdminURL({ adminRoute, path: `/collections/${entity.slug}` })
107+
108+
createHREF = formatAdminURL({
109+
adminRoute,
110+
path: `/collections/${entity.slug}/create`,
111+
})
112+
106113
hasCreatePermission =
107114
permissions?.collections?.[entity.slug]?.create?.permission
108115
}
109116

110117
if (type === EntityType.global) {
111118
title = getTranslation(entity.label, i18n)
119+
112120
buttonAriaLabel = t('general:editLabel', {
113121
label: getTranslation(entity.label, i18n),
114122
})
115-
href = `${adminRoute}/globals/${entity.slug}`
123+
124+
href = formatAdminURL({
125+
adminRoute,
126+
path: `/globals/${entity.slug}`,
127+
})
116128
}
117129

118130
return (

0 commit comments

Comments
 (0)