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 { getPayloadHMR, reload } from '../utilities/getPayloadHMR.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 { getNextRequestI18n } from '../../utilities/getNextRequestI18n.js'
|
||||
import { initPage } from '../../utilities/initPage.js'
|
||||
import { initPage } from '../../utilities/initPage/index.js'
|
||||
import { NotFoundClient } from './index.client.js'
|
||||
|
||||
export const generatePageMetadata = async ({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { SanitizedConfig } from 'payload/config'
|
||||
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 { CreateFirstUserView } from '../CreateFirstUser/index.js'
|
||||
|
||||
@@ -7,7 +7,7 @@ import { MinimalTemplate } from '@payloadcms/ui/templates/Minimal'
|
||||
import { notFound, redirect } from 'next/navigation.js'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
import { initPage } from '../../utilities/initPage.js'
|
||||
import { initPage } from '../../utilities/initPage/index.js'
|
||||
import { getViewFromConfig } from './getViewFromConfig.js'
|
||||
|
||||
export { generatePageMetadata } from './meta.js'
|
||||
|
||||
Reference in New Issue
Block a user