fix(next): 404 handling
This commit is contained in:
17
app/(payload)/admin/[[...segments]]/not-found.tsx
Normal file
17
app/(payload)/admin/[[...segments]]/not-found.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
import config from '@payload-config'
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import { NotFoundView } from '@payloadcms/next/views/NotFound/index'
|
||||
|
||||
type Args = {
|
||||
params: {
|
||||
segments: string[]
|
||||
}
|
||||
searchParams: {
|
||||
[key: string]: string | string[]
|
||||
}
|
||||
}
|
||||
|
||||
const NotFound = ({ params, searchParams }: Args) => NotFoundView({ config, params, searchParams })
|
||||
|
||||
export default NotFound
|
||||
@@ -10,7 +10,7 @@ import { initI18n } from '@payloadcms/translations'
|
||||
import { translations } from '@payloadcms/translations/client'
|
||||
import { findLocaleFromCode } from '@payloadcms/ui'
|
||||
import { headers as getHeaders } from 'next/headers'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { notFound, redirect } from 'next/navigation'
|
||||
import { createLocalReq } from 'payload/utilities'
|
||||
import qs from 'qs'
|
||||
|
||||
@@ -88,10 +88,18 @@ export const initPage = async ({
|
||||
|
||||
if (collectionSlug) {
|
||||
collectionConfig = collections.find((collection) => collection.slug === collectionSlug)
|
||||
|
||||
if (!collectionConfig) {
|
||||
notFound()
|
||||
}
|
||||
}
|
||||
|
||||
if (globalSlug) {
|
||||
globalConfig = globals.find((global) => global.slug === globalSlug)
|
||||
|
||||
if (!globalConfig) {
|
||||
notFound()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,13 +2,13 @@ import type { Metadata } from 'next'
|
||||
import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload/types'
|
||||
|
||||
import { getNextI18n } from '../../utilities/getNextI18n'
|
||||
import { meta } from '../../utilities/meta'
|
||||
import { generateMetadata as apiMeta } from '../API/meta'
|
||||
import { generateMetadata as editMeta } from '../Edit/meta'
|
||||
import { generateMetadata as livePreviewMeta } from '../LivePreview/meta'
|
||||
import { generateMetadata as versionMeta } from '../Version/meta'
|
||||
import { generateMetadata as versionsMeta } from '../Versions/meta'
|
||||
import { GenerateViewMetadata } from '../Root'
|
||||
import { generateNotFoundMeta } from '../NotFound/meta'
|
||||
|
||||
export type GenerateEditViewMetadata = (
|
||||
args: Parameters<GenerateViewMetadata>[0] & {
|
||||
@@ -102,10 +102,5 @@ export const getMetaBySegment: GenerateEditViewMetadata = async ({
|
||||
})
|
||||
}
|
||||
|
||||
return meta({
|
||||
config,
|
||||
description: '',
|
||||
keywords: '',
|
||||
title: '',
|
||||
})
|
||||
return generateNotFoundMeta({ i18n })
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
buildStateFromSchema,
|
||||
formatFields,
|
||||
} from '@payloadcms/ui'
|
||||
import { notFound } from 'next/navigation'
|
||||
import queryString from 'qs'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
@@ -27,6 +26,7 @@ import type { GenerateEditViewMetadata } from './getMetaBySegment'
|
||||
|
||||
import { getMetaBySegment } from './getMetaBySegment'
|
||||
import { getViewsFromConfig } from './getViewsFromConfig'
|
||||
import { NotFoundClient } from '../NotFound/index.client'
|
||||
|
||||
export const generateMetadata: GenerateEditViewMetadata = async (args) => getMetaBySegment(args)
|
||||
|
||||
@@ -97,20 +97,24 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
DefaultView = collectionViews?.DefaultView
|
||||
|
||||
if (!CustomView && !DefaultView) {
|
||||
return notFound()
|
||||
return <NotFoundClient />
|
||||
}
|
||||
|
||||
try {
|
||||
data = await payload.findByID({
|
||||
id,
|
||||
collection: collectionSlug,
|
||||
depth: 0,
|
||||
locale: locale.code,
|
||||
user,
|
||||
})
|
||||
} catch (error) {} // eslint-disable-line no-empty
|
||||
|
||||
if (id) {
|
||||
try {
|
||||
data = await payload.findByID({
|
||||
id,
|
||||
collection: collectionSlug,
|
||||
depth: 0,
|
||||
locale: locale.code,
|
||||
user,
|
||||
})
|
||||
} catch (error) {} // eslint-disable-line no-empty
|
||||
|
||||
if (!data) {
|
||||
return <NotFoundClient />
|
||||
}
|
||||
|
||||
preferencesKey = `collection-${collectionSlug}-${id}`
|
||||
}
|
||||
}
|
||||
@@ -137,15 +141,21 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
DefaultView = globalViews?.DefaultView
|
||||
|
||||
if (!CustomView && !DefaultView) {
|
||||
return notFound()
|
||||
return <NotFoundClient />
|
||||
}
|
||||
|
||||
data = await payload.findGlobal({
|
||||
slug: globalSlug,
|
||||
depth: 0,
|
||||
locale: locale.code,
|
||||
user,
|
||||
})
|
||||
try {
|
||||
data = await payload.findGlobal({
|
||||
slug: globalSlug,
|
||||
depth: 0,
|
||||
locale: locale.code,
|
||||
user,
|
||||
})
|
||||
} catch (error) {} // eslint-disable-line no-empty
|
||||
|
||||
if (!data) {
|
||||
return <NotFoundClient />
|
||||
}
|
||||
|
||||
preferencesKey = `global-${globalSlug}`
|
||||
}
|
||||
|
||||
45
packages/next/src/views/NotFound/index.client.tsx
Normal file
45
packages/next/src/views/NotFound/index.client.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
'use client'
|
||||
import { Button, Gutter, useConfig, useStepNav, useTranslation } from '@payloadcms/ui'
|
||||
import Link from 'next/link'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'not-found'
|
||||
|
||||
export const NotFoundClient: React.FC<{
|
||||
marginTop?: 'large'
|
||||
}> = (props) => {
|
||||
const { marginTop = 'large' } = props
|
||||
|
||||
const { setStepNav } = useStepNav()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const {
|
||||
routes: { admin },
|
||||
} = useConfig()
|
||||
|
||||
useEffect(() => {
|
||||
setStepNav([
|
||||
{
|
||||
label: t('general:notFound'),
|
||||
},
|
||||
])
|
||||
}, [setStepNav, t])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[baseClass, marginTop && `${baseClass}--margin-top-${marginTop}`]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
<Gutter className={`${baseClass}__wrap`}>
|
||||
<h1>{t('general:nothingFound')}</h1>
|
||||
<p>{t('general:sorryNotFound')}</p>
|
||||
<Button Link={Link} className={`${baseClass}__button`} el="link" to={`${admin}`}>
|
||||
{t('general:backToDashboard')}
|
||||
</Button>
|
||||
</Gutter>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,53 +1,39 @@
|
||||
'use client'
|
||||
import { Button, Gutter, useConfig, useStepNav, useTranslation } from '@payloadcms/ui'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
||||
// import Meta from '../../utilities/Meta'
|
||||
import './index.scss'
|
||||
import { NotFoundClient } from './index.client'
|
||||
import { DefaultTemplate } from '@payloadcms/ui'
|
||||
import { SanitizedConfig } from 'payload/types'
|
||||
import { initPage } from '../../utilities/initPage'
|
||||
|
||||
const baseClass = 'not-found'
|
||||
export const NotFoundView = async ({
|
||||
config: configPromise,
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
config: Promise<SanitizedConfig>
|
||||
params: {
|
||||
[key: string]: string | string[]
|
||||
}
|
||||
searchParams: {
|
||||
[key: string]: string | string[]
|
||||
}
|
||||
}) => {
|
||||
const config = await configPromise
|
||||
|
||||
const NotFound: React.FC<{
|
||||
marginTop?: 'large'
|
||||
}> = (props) => {
|
||||
const { marginTop } = props
|
||||
|
||||
const { setStepNav } = useStepNav()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const {
|
||||
routes: { admin },
|
||||
} = useConfig()
|
||||
|
||||
// useEffect(() => {
|
||||
// setStepNav([
|
||||
// {
|
||||
// label: t('general:notFound'),
|
||||
// },
|
||||
// ])
|
||||
// }, [setStepNav, t])
|
||||
const initPageResult = await initPage({
|
||||
config,
|
||||
searchParams,
|
||||
route: '',
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[baseClass, marginTop && `${baseClass}--margin-top-${marginTop}`]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
<DefaultTemplate
|
||||
config={initPageResult.req.payload.config}
|
||||
i18n={initPageResult.req.i18n}
|
||||
permissions={initPageResult.permissions}
|
||||
user={initPageResult.req.user}
|
||||
>
|
||||
{/* <Meta
|
||||
description={t('general:pageNotFound')}
|
||||
keywords={`404 ${t('general:notFound')}`}
|
||||
title={t('general:notFound')}
|
||||
/> */}
|
||||
<Gutter className={`${baseClass}__wrap`}>
|
||||
<h1>{t('general:nothingFound')}</h1>
|
||||
<p>{t('general:sorryNotFound')}</p>
|
||||
<Button Link={Link} className={`${baseClass}__button`} el="link" to={`${admin}`}>
|
||||
{t('general:backToDashboard')}
|
||||
</Button>
|
||||
</Gutter>
|
||||
</div>
|
||||
<NotFoundClient />
|
||||
</DefaultTemplate>
|
||||
)
|
||||
}
|
||||
|
||||
export default NotFound
|
||||
|
||||
9
packages/next/src/views/NotFound/meta.ts
Normal file
9
packages/next/src/views/NotFound/meta.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { I18n } from '@payloadcms/translations'
|
||||
|
||||
export const generateNotFoundMeta = ({ i18n }: { i18n: I18n }) => {
|
||||
return {
|
||||
description: i18n.t('general:pageNotFound'),
|
||||
keywords: `404 ${i18n.t('general:notFound')}`,
|
||||
title: i18n.t('general:notFound'),
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import type { Metadata } from 'next'
|
||||
import type { InitPageResult, SanitizedConfig } from 'payload/types'
|
||||
|
||||
import { DefaultTemplate, MinimalTemplate } from '@payloadcms/ui'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { notFound, redirect } from 'next/navigation'
|
||||
import React from 'react'
|
||||
|
||||
import { initPage } from '../../utilities/initPage'
|
||||
@@ -21,16 +21,6 @@ import { Verify, verifyBaseClass } from '../Verify'
|
||||
|
||||
export { generatePageMetadata } from './meta'
|
||||
|
||||
type Args = {
|
||||
config: Promise<SanitizedConfig>
|
||||
params: {
|
||||
[key: string]: string | string[]
|
||||
}
|
||||
searchParams: {
|
||||
[key: string]: string | string[]
|
||||
}
|
||||
}
|
||||
|
||||
export type GenerateViewMetadata = (args: {
|
||||
config: SanitizedConfig
|
||||
i18n: I18n
|
||||
@@ -59,7 +49,19 @@ const oneSegmentViews = {
|
||||
unauthorized: Unauthorized,
|
||||
}
|
||||
|
||||
export const RootPage = async ({ config: configPromise, params, searchParams }: Args) => {
|
||||
export const RootPage = async ({
|
||||
config: configPromise,
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
config: Promise<SanitizedConfig>
|
||||
params: {
|
||||
[key: string]: string | string[]
|
||||
}
|
||||
searchParams: {
|
||||
[key: string]: string | string[]
|
||||
}
|
||||
}) => {
|
||||
const config = await configPromise
|
||||
|
||||
const {
|
||||
@@ -69,7 +71,6 @@ export const RootPage = async ({ config: configPromise, params, searchParams }:
|
||||
|
||||
let ViewToRender: React.FC<AdminViewProps>
|
||||
let templateClassName
|
||||
let initPageResult: InitPageResult
|
||||
let templateType: 'default' | 'minimal' = 'default'
|
||||
|
||||
let route = adminRoute
|
||||
@@ -85,6 +86,12 @@ export const RootPage = async ({ config: configPromise, params, searchParams }:
|
||||
const isGlobal = segmentOne === 'globals'
|
||||
const isCollection = segmentOne === 'collections'
|
||||
|
||||
let initPageOptions: Parameters<typeof initPage>[0] = {
|
||||
config,
|
||||
searchParams,
|
||||
route,
|
||||
}
|
||||
|
||||
// TODO: handle custom routes
|
||||
|
||||
switch (segments.length) {
|
||||
@@ -92,12 +99,7 @@ export const RootPage = async ({ config: configPromise, params, searchParams }:
|
||||
ViewToRender = Dashboard
|
||||
templateClassName = 'dashboard'
|
||||
templateType = 'default'
|
||||
initPageResult = await initPage({
|
||||
config,
|
||||
redirectUnauthenticatedUser: true,
|
||||
route,
|
||||
searchParams,
|
||||
})
|
||||
initPageOptions.redirectUnauthenticatedUser = true
|
||||
break
|
||||
}
|
||||
case 1: {
|
||||
@@ -108,18 +110,12 @@ export const RootPage = async ({ config: configPromise, params, searchParams }:
|
||||
// --> /logout
|
||||
// --> /logout-inactivity
|
||||
// --> /unauthorized
|
||||
initPageResult = await initPage({ config, route, searchParams })
|
||||
ViewToRender = oneSegmentViews[segmentOne]
|
||||
templateClassName = baseClasses[segmentOne]
|
||||
templateType = 'minimal'
|
||||
} else if (segmentOne === 'account') {
|
||||
// --> /account
|
||||
initPageResult = await initPage({
|
||||
config,
|
||||
redirectUnauthenticatedUser: true,
|
||||
route,
|
||||
searchParams,
|
||||
})
|
||||
initPageOptions.redirectUnauthenticatedUser = true
|
||||
ViewToRender = Account
|
||||
templateClassName = 'account'
|
||||
templateType = 'default'
|
||||
@@ -129,30 +125,19 @@ export const RootPage = async ({ config: configPromise, params, searchParams }:
|
||||
case 2: {
|
||||
if (segmentOne === 'reset') {
|
||||
// --> /reset/:token
|
||||
initPageResult = await initPage({ config, route, searchParams })
|
||||
ViewToRender = ResetPassword
|
||||
templateClassName = baseClasses[segmentTwo]
|
||||
templateType = 'minimal'
|
||||
}
|
||||
if (isCollection) {
|
||||
// --> /collections/:collectionSlug
|
||||
initPageResult = await initPage({
|
||||
config,
|
||||
redirectUnauthenticatedUser: true,
|
||||
route,
|
||||
searchParams,
|
||||
})
|
||||
initPageOptions.redirectUnauthenticatedUser = true
|
||||
ViewToRender = ListView
|
||||
templateClassName = `${segmentTwo}-list`
|
||||
templateType = 'default'
|
||||
} else if (isGlobal) {
|
||||
// --> /globals/:globalSlug
|
||||
initPageResult = await initPage({
|
||||
config,
|
||||
redirectUnauthenticatedUser: true,
|
||||
route,
|
||||
searchParams,
|
||||
})
|
||||
initPageOptions.redirectUnauthenticatedUser = true
|
||||
ViewToRender = DocumentView
|
||||
templateClassName = 'global-edit'
|
||||
templateType = 'default'
|
||||
@@ -162,7 +147,6 @@ export const RootPage = async ({ config: configPromise, params, searchParams }:
|
||||
default:
|
||||
if (segmentTwo === 'verify') {
|
||||
// --> /:collectionSlug/verify/:token
|
||||
initPageResult = await initPage({ config, route, searchParams })
|
||||
ViewToRender = Verify
|
||||
templateClassName = 'verify'
|
||||
templateType = 'minimal'
|
||||
@@ -173,12 +157,7 @@ export const RootPage = async ({ config: configPromise, params, searchParams }:
|
||||
// --> /collections/:collectionSlug/:id/versions
|
||||
// --> /collections/:collectionSlug/:id/versions/:versionId
|
||||
// --> /collections/:collectionSlug/:id/api
|
||||
initPageResult = await initPage({
|
||||
config,
|
||||
redirectUnauthenticatedUser: true,
|
||||
route,
|
||||
searchParams,
|
||||
})
|
||||
initPageOptions.redirectUnauthenticatedUser = true
|
||||
ViewToRender = DocumentView
|
||||
templateClassName = `collection-default-edit`
|
||||
templateType = 'default'
|
||||
@@ -188,12 +167,7 @@ export const RootPage = async ({ config: configPromise, params, searchParams }:
|
||||
// --> /globals/:globalSlug/preview
|
||||
// --> /globals/:globalSlug/versions/:versionId
|
||||
// --> /globals/:globalSlug/api
|
||||
initPageResult = await initPage({
|
||||
config,
|
||||
redirectUnauthenticatedUser: true,
|
||||
route,
|
||||
searchParams,
|
||||
})
|
||||
initPageOptions.redirectUnauthenticatedUser = true
|
||||
ViewToRender = DocumentView
|
||||
templateClassName = `global-edit`
|
||||
templateType = 'default'
|
||||
@@ -201,51 +175,49 @@ export const RootPage = async ({ config: configPromise, params, searchParams }:
|
||||
break
|
||||
}
|
||||
|
||||
const dbHasUser = await initPageResult.req.payload.db
|
||||
.findOne({
|
||||
collection: userSlug,
|
||||
req: initPageResult.req,
|
||||
})
|
||||
?.then((doc) => !!doc)
|
||||
let dbHasUser = false
|
||||
|
||||
const createFirstUserRoute = `${adminRoute}/create-first-user`
|
||||
|
||||
if (!dbHasUser && route !== createFirstUserRoute) {
|
||||
redirect(createFirstUserRoute)
|
||||
if (!ViewToRender) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
if (dbHasUser && route === createFirstUserRoute) {
|
||||
redirect(adminRoute)
|
||||
}
|
||||
const initPageResult = await initPage(initPageOptions)
|
||||
|
||||
if (initPageResult) {
|
||||
if (templateType === 'minimal') {
|
||||
return (
|
||||
<MinimalTemplate className={templateClassName}>
|
||||
<ViewToRender
|
||||
initPageResult={initPageResult}
|
||||
params={params}
|
||||
searchParams={searchParams}
|
||||
/>
|
||||
</MinimalTemplate>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<DefaultTemplate
|
||||
config={config}
|
||||
i18n={initPageResult.req.i18n}
|
||||
permissions={initPageResult.permissions}
|
||||
user={initPageResult.req.user}
|
||||
>
|
||||
<ViewToRender
|
||||
initPageResult={initPageResult}
|
||||
params={params}
|
||||
searchParams={searchParams}
|
||||
/>
|
||||
</DefaultTemplate>
|
||||
)
|
||||
dbHasUser = await initPageResult?.req.payload.db
|
||||
.findOne({
|
||||
collection: userSlug,
|
||||
req: initPageResult?.req,
|
||||
})
|
||||
?.then((doc) => !!doc)
|
||||
|
||||
const createFirstUserRoute = `${adminRoute}/create-first-user`
|
||||
|
||||
if (!dbHasUser && route !== createFirstUserRoute) {
|
||||
redirect(createFirstUserRoute)
|
||||
}
|
||||
|
||||
if (dbHasUser && route === createFirstUserRoute) {
|
||||
redirect(adminRoute)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
if (templateType === 'minimal') {
|
||||
return (
|
||||
<MinimalTemplate className={templateClassName}>
|
||||
<ViewToRender initPageResult={initPageResult} params={params} searchParams={searchParams} />
|
||||
</MinimalTemplate>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<DefaultTemplate
|
||||
config={config}
|
||||
i18n={initPageResult.req.i18n}
|
||||
permissions={initPageResult.permissions}
|
||||
user={initPageResult.req.user}
|
||||
>
|
||||
<ViewToRender initPageResult={initPageResult} params={params} searchParams={searchParams} />
|
||||
</DefaultTemplate>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Metadata } from 'next'
|
||||
import { generateDocumentMetadata } from '../Document/meta'
|
||||
import { getNextI18n } from '../../utilities/getNextI18n'
|
||||
import { SanitizedConfig } from 'payload/types'
|
||||
import { generateNotFoundMeta } from '../NotFound/meta'
|
||||
|
||||
const oneSegmentMeta = {
|
||||
'create-first-user': generateCreateFirstUserMetadata,
|
||||
@@ -131,5 +132,9 @@ export const generatePageMetadata = async ({ config: configPromise, params }: Ar
|
||||
}
|
||||
}
|
||||
|
||||
if (!meta) {
|
||||
meta = generateNotFoundMeta({ i18n })
|
||||
}
|
||||
|
||||
return meta
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user