chore(next): restructures initPage
This commit is contained in:
@@ -5,4 +5,4 @@ export { createPayloadRequest } from '../utilities/createPayloadRequest.js'
|
|||||||
export { getNextRequestI18n } from '../utilities/getNextRequestI18n.js'
|
export { getNextRequestI18n } from '../utilities/getNextRequestI18n.js'
|
||||||
export { getPayloadHMR, reload } from '../utilities/getPayloadHMR.js'
|
export { getPayloadHMR, reload } from '../utilities/getPayloadHMR.js'
|
||||||
export { headersWithCors } from '../utilities/headersWithCors.js'
|
export { headersWithCors } from '../utilities/headersWithCors.js'
|
||||||
export { initPage } from '../utilities/initPage.js'
|
export { initPage } from '../utilities/initPage/index.js'
|
||||||
|
|||||||
@@ -1,149 +0,0 @@
|
|||||||
import type {
|
|
||||||
InitPageResult,
|
|
||||||
PayloadRequestWithData,
|
|
||||||
SanitizedCollectionConfig,
|
|
||||||
SanitizedConfig,
|
|
||||||
SanitizedGlobalConfig,
|
|
||||||
VisibleEntities,
|
|
||||||
} from 'payload/types'
|
|
||||||
|
|
||||||
import { initI18n } from '@payloadcms/translations'
|
|
||||||
import { findLocaleFromCode } from '@payloadcms/ui/utilities/findLocaleFromCode'
|
|
||||||
import { headers as getHeaders } from 'next/headers.js'
|
|
||||||
import { notFound, redirect } from 'next/navigation.js'
|
|
||||||
import { parseCookies } from 'payload/auth'
|
|
||||||
import { createLocalReq, isEntityHidden } from 'payload/utilities'
|
|
||||||
import qs from 'qs'
|
|
||||||
|
|
||||||
import { getPayloadHMR } from '../utilities/getPayloadHMR.js'
|
|
||||||
import { getRequestLanguage } from './getRequestLanguage.js'
|
|
||||||
|
|
||||||
type Args = {
|
|
||||||
config: Promise<SanitizedConfig> | SanitizedConfig
|
|
||||||
redirectUnauthenticatedUser?: boolean
|
|
||||||
route: string
|
|
||||||
searchParams: { [key: string]: string | string[] | undefined }
|
|
||||||
}
|
|
||||||
|
|
||||||
const authRoutes = [
|
|
||||||
'/login',
|
|
||||||
'/logout',
|
|
||||||
'/create-first-user',
|
|
||||||
'/forgot',
|
|
||||||
'/reset',
|
|
||||||
'/verify',
|
|
||||||
'/logout-inactivity',
|
|
||||||
]
|
|
||||||
|
|
||||||
export const initPage = async ({
|
|
||||||
config: configPromise,
|
|
||||||
redirectUnauthenticatedUser = false,
|
|
||||||
route,
|
|
||||||
searchParams,
|
|
||||||
}: Args): Promise<InitPageResult> => {
|
|
||||||
const headers = getHeaders()
|
|
||||||
const localeParam = searchParams?.locale as string
|
|
||||||
const payload = await getPayloadHMR({ config: configPromise })
|
|
||||||
const { collections, globals, localization, routes } = payload.config
|
|
||||||
|
|
||||||
const queryString = `${qs.stringify(searchParams ?? {}, { addQueryPrefix: true })}`
|
|
||||||
const defaultLocale =
|
|
||||||
localization && localization.defaultLocale ? localization.defaultLocale : 'en'
|
|
||||||
const localeCode = localeParam || defaultLocale
|
|
||||||
const locale = localization && findLocaleFromCode(localization, localeCode)
|
|
||||||
const cookies = parseCookies(headers)
|
|
||||||
const language = getRequestLanguage({ config: payload.config, cookies, headers })
|
|
||||||
|
|
||||||
const i18n = await initI18n({
|
|
||||||
config: payload.config.i18n,
|
|
||||||
context: 'client',
|
|
||||||
language,
|
|
||||||
})
|
|
||||||
|
|
||||||
const req = await createLocalReq(
|
|
||||||
{
|
|
||||||
fallbackLocale: null,
|
|
||||||
locale: locale.code,
|
|
||||||
req: {
|
|
||||||
i18n,
|
|
||||||
query: qs.parse(queryString, {
|
|
||||||
depth: 10,
|
|
||||||
ignoreQueryPrefix: true,
|
|
||||||
}),
|
|
||||||
url: `${payload.config.serverURL}${route}${searchParams ? queryString : ''}`,
|
|
||||||
} as PayloadRequestWithData,
|
|
||||||
},
|
|
||||||
payload,
|
|
||||||
)
|
|
||||||
|
|
||||||
const { permissions, user } = await payload.auth({ headers, req })
|
|
||||||
|
|
||||||
req.user = user
|
|
||||||
|
|
||||||
const visibleEntities: VisibleEntities = {
|
|
||||||
collections: payload.config.collections
|
|
||||||
.map(({ slug, admin: { hidden } }) => (!isEntityHidden({ hidden, user }) ? slug : null))
|
|
||||||
.filter(Boolean),
|
|
||||||
globals: payload.config.globals
|
|
||||||
.map(({ slug, admin: { hidden } }) => (!isEntityHidden({ hidden, user }) ? slug : null))
|
|
||||||
.filter(Boolean),
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
routes: { admin: adminRoute },
|
|
||||||
} = payload.config
|
|
||||||
|
|
||||||
const routeSegments = route.replace(adminRoute, '').split('/').filter(Boolean)
|
|
||||||
const [entityType, entitySlug, createOrID] = routeSegments
|
|
||||||
const collectionSlug = entityType === 'collections' ? entitySlug : undefined
|
|
||||||
const globalSlug = entityType === 'globals' ? entitySlug : undefined
|
|
||||||
const docID = collectionSlug && createOrID !== 'create' ? createOrID : undefined
|
|
||||||
|
|
||||||
const isAdminRoute = route.startsWith(adminRoute)
|
|
||||||
const isAuthRoute = authRoutes.some((r) => route.replace(adminRoute, '').startsWith(r))
|
|
||||||
|
|
||||||
if (redirectUnauthenticatedUser && !user && !isAuthRoute) {
|
|
||||||
if (searchParams && 'redirect' in searchParams) delete searchParams.redirect
|
|
||||||
|
|
||||||
const stringifiedSearchParams = Object.keys(searchParams ?? {}).length
|
|
||||||
? `?${qs.stringify(searchParams)}`
|
|
||||||
: ''
|
|
||||||
|
|
||||||
redirect(`${routes.admin}/login?redirect=${route + stringifiedSearchParams}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!permissions.canAccessAdmin && isAdminRoute && !isAuthRoute) {
|
|
||||||
notFound()
|
|
||||||
}
|
|
||||||
|
|
||||||
let collectionConfig: SanitizedCollectionConfig
|
|
||||||
let globalConfig: SanitizedGlobalConfig
|
|
||||||
|
|
||||||
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 {
|
|
||||||
collectionConfig,
|
|
||||||
cookies,
|
|
||||||
docID,
|
|
||||||
globalConfig,
|
|
||||||
locale,
|
|
||||||
permissions,
|
|
||||||
req,
|
|
||||||
translations: i18n.translations,
|
|
||||||
visibleEntities,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
59
packages/next/src/utilities/initPage/handleAdminPage.ts
Normal file
59
packages/next/src/utilities/initPage/handleAdminPage.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import type { Permissions } from 'payload/auth'
|
||||||
|
import type {
|
||||||
|
SanitizedCollectionConfig,
|
||||||
|
SanitizedConfig,
|
||||||
|
SanitizedGlobalConfig,
|
||||||
|
} from 'payload/types'
|
||||||
|
|
||||||
|
import { notFound } from 'next/navigation.js'
|
||||||
|
|
||||||
|
import { isAdminAuthRoute, isAdminRoute } from './shared.js'
|
||||||
|
|
||||||
|
export const handleAdminPage = ({
|
||||||
|
adminRoute,
|
||||||
|
config,
|
||||||
|
permissions,
|
||||||
|
route,
|
||||||
|
}: {
|
||||||
|
adminRoute: string
|
||||||
|
config: SanitizedConfig
|
||||||
|
permissions: Permissions
|
||||||
|
route: string
|
||||||
|
}) => {
|
||||||
|
if (isAdminRoute(route, adminRoute)) {
|
||||||
|
const routeSegments = route.replace(adminRoute, '').split('/').filter(Boolean)
|
||||||
|
const [entityType, entitySlug, createOrID] = routeSegments
|
||||||
|
const collectionSlug = entityType === 'collections' ? entitySlug : undefined
|
||||||
|
const globalSlug = entityType === 'globals' ? entitySlug : undefined
|
||||||
|
const docID = collectionSlug && createOrID !== 'create' ? createOrID : undefined
|
||||||
|
|
||||||
|
let collectionConfig: SanitizedCollectionConfig | undefined
|
||||||
|
let globalConfig: SanitizedGlobalConfig | undefined
|
||||||
|
|
||||||
|
if (collectionSlug) {
|
||||||
|
collectionConfig = config.collections.find((collection) => collection.slug === collectionSlug)
|
||||||
|
|
||||||
|
if (!collectionConfig) {
|
||||||
|
notFound()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (globalSlug) {
|
||||||
|
globalConfig = config.globals.find((global) => global.slug === globalSlug)
|
||||||
|
|
||||||
|
if (!globalConfig) {
|
||||||
|
notFound()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!permissions.canAccessAdmin && !isAdminAuthRoute(route, adminRoute)) {
|
||||||
|
notFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
collectionConfig,
|
||||||
|
docID,
|
||||||
|
globalConfig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
packages/next/src/utilities/initPage/handleAuthRedirect.ts
Normal file
25
packages/next/src/utilities/initPage/handleAuthRedirect.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { redirect } from 'next/navigation.js'
|
||||||
|
import QueryString from 'qs'
|
||||||
|
|
||||||
|
import { isAdminAuthRoute } from './shared.js'
|
||||||
|
|
||||||
|
export const handleAuthRedirect = ({
|
||||||
|
adminRoute,
|
||||||
|
route,
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
adminRoute: string
|
||||||
|
redirectUnauthenticatedUser: boolean | string
|
||||||
|
route: string
|
||||||
|
searchParams: { [key: string]: string | string[] }
|
||||||
|
}) => {
|
||||||
|
if (!isAdminAuthRoute(route, adminRoute)) {
|
||||||
|
if (searchParams && 'redirect' in searchParams) delete searchParams.redirect
|
||||||
|
|
||||||
|
const stringifiedSearchParams = Object.keys(searchParams ?? {}).length
|
||||||
|
? `?${QueryString.stringify(searchParams)}`
|
||||||
|
: ''
|
||||||
|
|
||||||
|
redirect(`${adminRoute}/login?redirect=${route + stringifiedSearchParams}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
105
packages/next/src/utilities/initPage/index.ts
Normal file
105
packages/next/src/utilities/initPage/index.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import type { InitPageResult, PayloadRequestWithData, VisibleEntities } from 'payload/types'
|
||||||
|
|
||||||
|
import { initI18n } from '@payloadcms/translations'
|
||||||
|
import { findLocaleFromCode } from '@payloadcms/ui/utilities/findLocaleFromCode'
|
||||||
|
import { headers as getHeaders } from 'next/headers.js'
|
||||||
|
import { parseCookies } from 'payload/auth'
|
||||||
|
import { createLocalReq, isEntityHidden } from 'payload/utilities'
|
||||||
|
import qs from 'qs'
|
||||||
|
|
||||||
|
import type { Args } from './types.js'
|
||||||
|
|
||||||
|
import { getPayloadHMR } from '../getPayloadHMR.js'
|
||||||
|
import { getRequestLanguage } from '../getRequestLanguage.js'
|
||||||
|
import { handleAdminPage } from './handleAdminPage.js'
|
||||||
|
import { handleAuthRedirect } from './handleAuthRedirect.js'
|
||||||
|
|
||||||
|
export const initPage = async ({
|
||||||
|
config: configPromise,
|
||||||
|
redirectUnauthenticatedUser = false,
|
||||||
|
route,
|
||||||
|
searchParams,
|
||||||
|
}: Args): Promise<InitPageResult> => {
|
||||||
|
const headers = getHeaders()
|
||||||
|
const localeParam = searchParams?.locale as string
|
||||||
|
const payload = await getPayloadHMR({ config: configPromise })
|
||||||
|
|
||||||
|
const {
|
||||||
|
collections,
|
||||||
|
globals,
|
||||||
|
i18n: i18nConfig,
|
||||||
|
localization,
|
||||||
|
routes: { admin: adminRoute },
|
||||||
|
} = payload.config
|
||||||
|
|
||||||
|
const queryString = `${qs.stringify(searchParams ?? {}, { addQueryPrefix: true })}`
|
||||||
|
const defaultLocale =
|
||||||
|
localization && localization.defaultLocale ? localization.defaultLocale : 'en'
|
||||||
|
const localeCode = localeParam || defaultLocale
|
||||||
|
const locale = localization && findLocaleFromCode(localization, localeCode)
|
||||||
|
const cookies = parseCookies(headers)
|
||||||
|
const language = getRequestLanguage({ config: payload.config, cookies, headers })
|
||||||
|
|
||||||
|
const i18n = await initI18n({
|
||||||
|
config: i18nConfig,
|
||||||
|
context: 'client',
|
||||||
|
language,
|
||||||
|
})
|
||||||
|
|
||||||
|
const req = await createLocalReq(
|
||||||
|
{
|
||||||
|
fallbackLocale: null,
|
||||||
|
locale: locale.code,
|
||||||
|
req: {
|
||||||
|
i18n,
|
||||||
|
query: qs.parse(queryString, {
|
||||||
|
depth: 10,
|
||||||
|
ignoreQueryPrefix: true,
|
||||||
|
}),
|
||||||
|
url: `${payload.config.serverURL}${route}${searchParams ? queryString : ''}`,
|
||||||
|
} as PayloadRequestWithData,
|
||||||
|
},
|
||||||
|
payload,
|
||||||
|
)
|
||||||
|
|
||||||
|
const { permissions, user } = await payload.auth({ headers, req })
|
||||||
|
|
||||||
|
req.user = user
|
||||||
|
|
||||||
|
const visibleEntities: VisibleEntities = {
|
||||||
|
collections: collections
|
||||||
|
.map(({ slug, admin: { hidden } }) => (!isEntityHidden({ hidden, user }) ? slug : null))
|
||||||
|
.filter(Boolean),
|
||||||
|
globals: globals
|
||||||
|
.map(({ slug, admin: { hidden } }) => (!isEntityHidden({ hidden, user }) ? slug : null))
|
||||||
|
.filter(Boolean),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (redirectUnauthenticatedUser && !user) {
|
||||||
|
handleAuthRedirect({
|
||||||
|
adminRoute,
|
||||||
|
redirectUnauthenticatedUser,
|
||||||
|
route,
|
||||||
|
searchParams,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const { collectionConfig, docID, globalConfig } = handleAdminPage({
|
||||||
|
adminRoute,
|
||||||
|
config: payload.config,
|
||||||
|
permissions,
|
||||||
|
route,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
collectionConfig,
|
||||||
|
cookies,
|
||||||
|
docID,
|
||||||
|
globalConfig,
|
||||||
|
locale,
|
||||||
|
permissions,
|
||||||
|
req,
|
||||||
|
translations: i18n.translations,
|
||||||
|
visibleEntities,
|
||||||
|
}
|
||||||
|
}
|
||||||
17
packages/next/src/utilities/initPage/shared.ts
Normal file
17
packages/next/src/utilities/initPage/shared.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export const authRoutes = [
|
||||||
|
'/login',
|
||||||
|
'/logout',
|
||||||
|
'/create-first-user',
|
||||||
|
'/forgot',
|
||||||
|
'/reset',
|
||||||
|
'/verify',
|
||||||
|
'/logout-inactivity',
|
||||||
|
]
|
||||||
|
|
||||||
|
export const isAdminRoute = (route: string, adminRoute: string) => {
|
||||||
|
return route.startsWith(adminRoute)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isAdminAuthRoute = (route: string, adminRoute: string) => {
|
||||||
|
return authRoutes.some((r) => route.replace(adminRoute, '').startsWith(r))
|
||||||
|
}
|
||||||
22
packages/next/src/utilities/initPage/types.ts
Normal file
22
packages/next/src/utilities/initPage/types.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import type { SanitizedConfig } from 'payload/types'
|
||||||
|
|
||||||
|
export type Args = {
|
||||||
|
/**
|
||||||
|
* Your sanitized Payload config.
|
||||||
|
* If unresolved, this function will await the promise.
|
||||||
|
*/
|
||||||
|
config: Promise<SanitizedConfig> | SanitizedConfig
|
||||||
|
/**
|
||||||
|
* If true, redirects unauthenticated users to the admin login page.
|
||||||
|
* If a string is provided, the user will be redirected to that specific URL.
|
||||||
|
*/
|
||||||
|
redirectUnauthenticatedUser?: boolean | string
|
||||||
|
/**
|
||||||
|
* The current route, i.e. `/admin/collections/posts`.
|
||||||
|
*/
|
||||||
|
route: string
|
||||||
|
/**
|
||||||
|
* The search parameters of the current route provided to all pages in Next.js.
|
||||||
|
*/
|
||||||
|
searchParams: { [key: string]: string | string[] | undefined }
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import { DefaultTemplate } from '@payloadcms/ui/templates/Default'
|
|||||||
import React, { Fragment } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
|
|
||||||
import { getNextRequestI18n } from '../../utilities/getNextRequestI18n.js'
|
import { getNextRequestI18n } from '../../utilities/getNextRequestI18n.js'
|
||||||
import { initPage } from '../../utilities/initPage.js'
|
import { initPage } from '../../utilities/initPage/index.js'
|
||||||
import { NotFoundClient } from './index.client.js'
|
import { NotFoundClient } from './index.client.js'
|
||||||
|
|
||||||
export const generatePageMetadata = async ({
|
export const generatePageMetadata = async ({
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { SanitizedConfig } from 'payload/config'
|
import type { SanitizedConfig } from 'payload/config'
|
||||||
import type { AdminViewComponent } from 'payload/types'
|
import type { AdminViewComponent } from 'payload/types'
|
||||||
|
|
||||||
import type { initPage } from '../../utilities/initPage.js'
|
import type { initPage } from '../../utilities/initPage/index.js'
|
||||||
|
|
||||||
import { Account } from '../Account/index.js'
|
import { Account } from '../Account/index.js'
|
||||||
import { CreateFirstUserView } from '../CreateFirstUser/index.js'
|
import { CreateFirstUserView } from '../CreateFirstUser/index.js'
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { MinimalTemplate } from '@payloadcms/ui/templates/Minimal'
|
|||||||
import { notFound, redirect } from 'next/navigation.js'
|
import { notFound, redirect } from 'next/navigation.js'
|
||||||
import React, { Fragment } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
|
|
||||||
import { initPage } from '../../utilities/initPage.js'
|
import { initPage } from '../../utilities/initPage/index.js'
|
||||||
import { getViewFromConfig } from './getViewFromConfig.js'
|
import { getViewFromConfig } from './getViewFromConfig.js'
|
||||||
|
|
||||||
export { generatePageMetadata } from './meta.js'
|
export { generatePageMetadata } from './meta.js'
|
||||||
|
|||||||
Reference in New Issue
Block a user