chore(next): restructures initPage

This commit is contained in:
Jacob Fletcher
2024-05-02 13:30:00 -04:00
parent e25814e1ee
commit 664c60d2bc
10 changed files with 232 additions and 153 deletions

View File

@@ -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'

View File

@@ -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,
}
}

View 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,
}
}
}

View 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}`)
}
}

View 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,
}
}

View 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))
}

View 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 }
}

View File

@@ -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 ({

View File

@@ -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'

View File

@@ -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'