Compare commits

..

74 Commits

Author SHA1 Message Date
Dan Ribbens
ca832a01cb chore(release): v3.0.0-alpha.18 [skip ci] 2024-03-06 10:46:17 -05:00
Jacob Fletcher
aaa2b204ba fix(next): uses correct global slug in getViewsFromConfig 2024-03-06 09:56:19 -05:00
Alessio Gravili
78bf9e5993 feat(plugin-seo): working plugin-seo 2024-03-06 09:50:37 -05:00
Alessio Gravili
8be0296fc1 fix(translations): translations variable not resolved if provided value is 0 (the number, not null) 2024-03-06 09:48:55 -05:00
Alessio Gravili
26cd741c04 fix: payload cache: set cached.reload to false immediately to prevent parallel reloads from unnecessarily performing the reload 2024-03-05 18:56:18 -05:00
Alessio Gravili
bf655b3327 fix: make sure schemaPath of Edit View updates when switching collections 2024-03-05 18:55:32 -05:00
Jacob Fletcher
1793b37adc fix(next): field permissions 2024-03-05 16:39:53 -05:00
Dan Ribbens
d0ffe85abb chore(release): v3.0.0-alpha.16 [skip ci] 2024-03-05 16:28:45 -05:00
James
f9f7dcfc58 chore: attempts to build to es6 instead of commonjs for next / ui 2024-03-05 16:27:48 -05:00
Dan Ribbens
f06257e7ff chore(release): v3.0.0-alpha.15 [skip ci] 2024-03-05 16:13:34 -05:00
James
e490f0bce6 chore: attempts to abstract sharp to optional dependency 2024-03-05 16:12:17 -05:00
Dan Ribbens
770c7173ec chore(release): v3.0.0-alpha.14 [skip ci] 2024-03-05 15:57:54 -05:00
Dan Ribbens
661ab4867b chore(release): v3.0.0-alpha.13 [skip ci] 2024-03-05 15:55:14 -05:00
James
9ee3b5aae6 chore: rolls back simplificaiton to root layout 2024-03-05 15:45:30 -05:00
Jarrod Flesch
d202256c30 chore: fix locale setter 2024-03-05 15:39:26 -05:00
James
b8856d4ef7 Merge branch 'feat/server-hmr' into feat/next-poc 2024-03-05 15:35:32 -05:00
James
e1294ac210 chore: removes console log 2024-03-05 15:19:16 -05:00
James
b0edd2d137 chore: finishes pattern for server hmr 2024-03-05 15:18:36 -05:00
Jarrod Flesch
0d0e9bc953 chore: thread missing Link components to Button 2024-03-05 15:17:36 -05:00
James
2576291d9f chore: refactors layout / root page 2024-03-05 14:37:40 -05:00
James
d2aab87faa chore: server hmr 2024-03-05 14:24:05 -05:00
Jacob Fletcher
c2509b462c chore(next): wires create first user 2024-03-05 11:42:20 -05:00
Jarrod Flesch
0e378be769 chore: opt out of caching getGraphql and getFieldSchemaMap 2024-03-05 09:25:42 -05:00
James
2785eaab21 chore: adds @payloadcms/db-mongodb to serverComponentsExternalPackages 2024-03-04 22:39:17 -05:00
James
8a10fd1547 chore: rolls back payload sharp version 2024-03-04 20:29:28 -05:00
James
8f8ed817fb chore: attempts turbopack-safe Schema access from mongoose 2024-03-04 17:42:04 -05:00
Alessio Gravili
7a150254fe chore: remove console log 2024-03-04 17:14:43 -05:00
Alessio Gravili
9612a4a781 chore: fix conflicting UI export (Upload => UploadField) 2024-03-04 17:07:45 -05:00
Alessio Gravili
249b233dc2 chore: revert to working next.js version 2024-03-04 17:05:51 -05:00
Jarrod Flesch
c4d4a9b47b chore: bump next, simplify i18n types 2024-03-04 17:04:05 -05:00
Dan Ribbens
a55e991bfa chore: fix read migration require regression 2024-03-04 17:02:22 -05:00
Alessio Gravili
610276f66b Merge remote-tracking branch 'origin/feat/next-poc' into feat/next-poc 2024-03-04 16:56:22 -05:00
Alessio Gravili
de99aabf7f feat: separate Input from Upload, Text & TextArea fields, get plugin-seo to load without errors 2024-03-04 16:56:14 -05:00
Elliot DeNolf
40a0a0083f chore(create-payload-app): init-next now create payload config and modifies tsconfig.json (#5242) 2024-03-04 16:55:43 -05:00
James
56ecd2ac14 chore: fix next config 2024-03-04 16:51:14 -05:00
James
4617d58b6a chore: exports withPayload in a more versatile way 2024-03-04 16:46:31 -05:00
James
98aeff2f3e chore: rolls back next config testing 2024-03-04 16:46:31 -05:00
James
f2239decca chore: abstracts next get route 2024-03-04 16:46:31 -05:00
Jacob Fletcher
36bd25a9cc chore(next): exports view base classes 2024-03-04 16:38:06 -05:00
Jarrod Flesch
933ae663f0 chore: e2e improvements 2024-03-04 16:22:15 -05:00
Dan Ribbens
a08674f708 test: database int 2024-03-04 15:58:56 -05:00
Jacob Fletcher
7fe0855932 chore(next): wires document events provider 2024-03-04 15:48:37 -05:00
Elliot DeNolf
d7594c4a1c chore(release): v3.0.0-alpha.12 [skip ci] 2024-03-04 15:43:40 -05:00
Elliot DeNolf
d9c8fa6043 chore: fix lexical formstate types 2024-03-04 15:43:23 -05:00
Alessio Gravili
4140d3084e chore: update pnpm lock 2024-03-04 15:36:39 -05:00
Alessio Gravili
93d3c9c657 Merge remote-tracking branch 'origin/feat/next-poc' into feat/next-poc 2024-03-04 15:21:35 -05:00
Alessio Gravili
3433f1224a chore(plugin-cloud-storage): fix build command 2024-03-04 15:21:29 -05:00
Jacob Fletcher
95ac686f04 chore: flattens meta into single function 2024-03-04 15:11:23 -05:00
James
ae8d11825a chore: installs sass as dependency in next package 2024-03-04 14:59:32 -05:00
Alessio Gravili
ece796432a chore(plugin-seo): prettier, lint, adjust package.json 2024-03-04 14:40:45 -05:00
Alessio Gravili
995ae21075 chore: remove useless mock file 2024-03-04 14:37:55 -05:00
Alessio Gravili
4a2d52717c feat: maybe-working plugin-cloud 2024-03-04 14:36:00 -05:00
Dan Ribbens
c0a07a6144 chore: validate schema moved from loadConfig to init 2024-03-04 14:30:38 -05:00
Jarrod Flesch
b14560c07d chore: more e2e linting 2024-03-04 14:24:28 -05:00
Jarrod Flesch
5de7d7f882 chore: test improvements and fixes 2024-03-04 14:24:28 -05:00
Alessio Gravili
a4ae3b223c chore: add @payloadcms/plugin-cloud to tsconfig 2024-03-04 14:22:24 -05:00
Alessio Gravili
383571ef05 chore(plugin-cloud-storage): upgrade azure emulator azurite to latest supported version 2024-03-04 14:17:12 -05:00
Alessio Gravili
7e5459cc62 chore: fix plugin-cloud-storage int tests 2024-03-04 14:11:09 -05:00
Alessio Gravili
caa8d9c0ed feat: working @payloadcms/plugin-cloud-storage 2024-03-04 14:08:43 -05:00
Jarrod Flesch
df887dc0e9 chore: fix autologin and redirect 2024-03-04 13:46:16 -05:00
James
262e3bbee2 chore: types update 2024-03-04 12:25:44 -05:00
Jacob Fletcher
87463c7e70 fix: admin view props 2024-03-04 12:15:30 -05:00
Dan Ribbens
2dc3e9af5e test: fix hooks int 2024-03-04 12:12:29 -05:00
Jarrod Flesch
d1de99d48d chore: fix EditViewProps import path in e2e 2024-03-04 12:08:12 -05:00
Jarrod Flesch
db87a06cfd fix: updates incorrect e2e import paths 2024-03-04 12:05:21 -05:00
Dan Ribbens
fab38c1f4e test: startMemoryDB with replica set 2024-03-04 12:03:29 -05:00
James
298dbfb0e0 Merge branch 'feat/next-poc' of github.com:payloadcms/payload into feat/next-poc 2024-03-04 12:02:29 -05:00
James
b978517945 chore: fixes dynamic meta import paths 2024-03-04 12:02:23 -05:00
Alessio Gravili
5c55449a87 chore: add @payloadcms/plugin-cloud-storage to tsconfig path 2024-03-04 12:00:29 -05:00
Alessio Gravili
78425d1947 Merge remote-tracking branch 'origin/feat/next-poc' into feat/next-poc 2024-03-04 11:58:29 -05:00
Alessio Gravili
55529eb493 chore: add back admin redirect, thanks Jarrod 2024-03-04 11:58:23 -05:00
Elliot DeNolf
06b2d907b6 chore(release): v3.0.0-alpha.11 [skip ci] 2024-03-04 11:53:26 -05:00
Jacob Fletcher
f54d96d916 chore: moves view related types to payload core 2024-03-04 11:51:58 -05:00
Jacob Fletcher
2c3da88b44 chore: consolidates server-side edit view props 2024-03-04 11:51:58 -05:00
288 changed files with 3668 additions and 3056 deletions

View File

@@ -1,7 +1,7 @@
/* 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 { RootPage } from '@payloadcms/next/views/Root/index'
import { RootPage, generatePageMetadata } from '@payloadcms/next/views/Root/index'
type Args = {
params: {
@@ -12,6 +12,9 @@ type Args = {
}
}
export const generateMetadata = ({ params, searchParams }: Args) =>
generatePageMetadata({ config, params, searchParams })
const Page = ({ params, searchParams }: Args) => RootPage({ config, params, searchParams })
export default Page

View File

@@ -1,7 +1,7 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import config from '@payload-config'
import { REST_GET, REST_DELETE, REST_PATCH, REST_POST } from '@payloadcms/next/routes'
import { REST_DELETE, REST_GET, REST_PATCH, REST_POST } from '@payloadcms/next/routes'
export const GET = REST_GET(config)
export const POST = REST_POST(config)

View File

@@ -1,9 +1,4 @@
export const GET = async () => {
console.log('1')
console.log('1')
console.log('1')
console.log('1')
console.log('1')
export const GET = () => {
return Response.json({
hello: 'elliot',
})

View File

@@ -1,4 +1,4 @@
const { withPayload } = require('./packages/next/src/withPayload')
const withPayload = require('./packages/next/src/withPayload')
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
@@ -6,5 +6,14 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({
module.exports = withBundleAnalyzer(
withPayload({
reactStrictMode: false,
async redirects() {
return [
{
destination: '/admin',
permanent: true,
source: '/',
},
]
},
}),
)

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.0.0-alpha.11",
"version": "3.0.0-alpha.18",
"private": true,
"workspaces:": [
"packages/*"
@@ -9,7 +9,7 @@
"build": "pnpm run build:core",
"build:all": "turbo build",
"build:core": "turbo build --filter \"!@payloadcms/plugin-*\"",
"build:plugins": "turbo build --filter \"@payloadcms/plugin-*\"",
"build:plugins": "turbo build --filter \"@payloadcms/plugin-*\" --filter \"!@payloadcms/plugin-search\"",
"build:app": "next build",
"build:create-payload-app": "turbo build --filter create-payload-app",
"build:db-mongodb": "turbo build --filter db-mongodb",
@@ -89,6 +89,7 @@
"@types/testing-library__jest-dom": "5.14.8",
"add-stream": "^1.0.0",
"chalk": "^4.1.2",
"comment-json": "^4.2.3",
"concat-stream": "^2.0.0",
"conventional-changelog": "^5.1.0",
"conventional-changelog-conventionalcommits": "^7.0.2",
@@ -115,7 +116,7 @@
"lint-staged": "^14.0.1",
"minimist": "1.2.8",
"mongodb-memory-server": "^9",
"next": "14.1.1-canary.26",
"next": "14.1.2",
"node-mocks-http": "^1.14.1",
"nodemon": "3.0.3",
"pino": "8.15.0",
@@ -128,7 +129,7 @@
"read-stream": "^2.1.1",
"rimraf": "3.0.2",
"semver": "^7.5.4",
"sharp": "0.33.2",
"sharp": "0.32.6",
"shelljs": "0.8.5",
"simple-git": "^3.20.0",
"slash": "3.0.0",

View File

@@ -7,7 +7,7 @@
},
"scripts": {
"build": "pnpm copyfiles && pnpm build:swc",
"copyfiles": "copyfiles -u 4 \"../next/src/app/(payload)/**\" \"dist/app\"",
"copyfiles": "copyfiles -u 2 \"../../app/(payload)/**\" \"dist\"",
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
"clean": "rimraf {dist,*.tsbuildinfo}",
"test": "jest",
@@ -23,7 +23,9 @@
"arg": "^5.0.0",
"chalk": "^4.1.0",
"command-exists": "^1.2.9",
"comment-json": "^4.2.3",
"degit": "^2.8.4",
"detect-package-manager": "^3.0.1",
"execa": "^5.0.0",
"figures": "^3.2.0",
"fs-extra": "^9.0.1",

View File

@@ -1,16 +1,88 @@
import type { CompilerOptions } from 'typescript'
import chalk from 'chalk'
import * as CommentJson from 'comment-json'
import { detect } from 'detect-package-manager'
import execa from 'execa'
import fs from 'fs'
import fse from 'fs-extra'
import globby from 'globby'
import path from 'path'
import type { CliArgs } from '../types'
import { copyRecursiveSync } from '../utils/copy-recursive-sync'
import { error, info, debug as origDebug, success } from '../utils/log'
import { error, info, debug as origDebug, success, warning } from '../utils/log'
export async function initNext(
args: Pick<CliArgs, '--debug'> & { nextDir?: string; useDistFiles?: boolean },
): Promise<{ success: boolean }> {
const { '--debug': debug, nextDir, useDistFiles } = args
type InitNextArgs = Pick<CliArgs, '--debug'> & {
projectDir?: string
useDistFiles?: boolean
}
type InitNextResult = { reason?: string; success: boolean; userAppDir?: string }
export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
args.projectDir = args.projectDir || process.cwd()
const { projectDir } = args
const templateResult = await applyPayloadTemplateFiles(args)
if (!templateResult.success) return templateResult
const { success: installSuccess } = await installDeps(projectDir)
if (!installSuccess) {
return { ...templateResult, reason: 'Failed to install dependencies', success: false }
}
// Create or find payload.config.ts
const createConfigResult = findOrCreatePayloadConfig(projectDir)
if (!createConfigResult.success) {
return { ...templateResult, ...createConfigResult }
}
// Add `@payload-config` to tsconfig.json `paths`
await addPayloadConfigToTsConfig(projectDir)
// Output directions for user to update next.config.js
const withPayloadMessage = `
${chalk.bold(`Wrap your existing next.config.js with the withPayload function. Here is an example:`)}
const { withPayload } = require("@payloadcms/next");
const nextConfig = {
// Your Next.js config
};
module.exports = withPayload(nextConfig);
`
console.log(withPayloadMessage)
return templateResult
}
async function addPayloadConfigToTsConfig(projectDir: string) {
const tsConfigPath = path.resolve(projectDir, 'tsconfig.json')
const userTsConfigContent = await fse.readFile(tsConfigPath, {
encoding: 'utf8',
})
const userTsConfig = CommentJson.parse(userTsConfigContent) as {
compilerOptions?: CompilerOptions
}
if (!userTsConfig.compilerOptions && !('extends' in userTsConfig)) {
userTsConfig.compilerOptions = {}
}
if (!userTsConfig.compilerOptions.paths?.['@payload-config']) {
userTsConfig.compilerOptions.paths = {
...(userTsConfig.compilerOptions.paths || {}),
'@payload-config': ['./payload.config.ts'],
}
await fse.writeFile(tsConfigPath, CommentJson.stringify(userTsConfig, null, 2))
}
}
async function applyPayloadTemplateFiles(args: InitNextArgs): Promise<InitNextResult> {
const { '--debug': debug, projectDir, useDistFiles } = args
info('Initializing Payload app in Next.js project', 1)
@@ -18,24 +90,18 @@ export async function initNext(
if (debug) origDebug(message)
}
let projectDir = process.cwd()
if (nextDir) {
projectDir = path.resolve(projectDir, nextDir)
if (debug) logDebug(`Overriding project directory to ${projectDir}`)
}
if (!fs.existsSync(projectDir)) {
error(`Could not find specified project directory at ${projectDir}`)
return { success: false }
return { reason: `Could not find specified project directory at ${projectDir}`, success: false }
}
// Next.js configs can be next.config.js, next.config.mjs, etc.
const foundConfig = (await globby('next.config.*js', { cwd: projectDir }))?.[0]
const nextConfigPath = path.resolve(projectDir, foundConfig)
if (!fs.existsSync(nextConfigPath)) {
error(
`No next.config.js found at ${nextConfigPath}. Ensure you are in a Next.js project directory.`,
)
return { success: false }
return {
reason: `No next.config.js found at ${nextConfigPath}. Ensure you are in a Next.js project directory.`,
success: false,
}
} else {
if (debug) logDebug(`Found Next config at ${nextConfigPath}`)
}
@@ -43,21 +109,27 @@ export async function initNext(
const templateFilesPath =
__dirname.endsWith('dist') || useDistFiles
? path.resolve(__dirname, '../..', 'dist/app')
: path.resolve(__dirname, '../../../next/src/app')
: path.resolve(__dirname, '../../../../app')
if (debug) logDebug(`Using template files from: ${templateFilesPath}`)
if (!fs.existsSync(templateFilesPath)) {
error(`Could not find template source files from ${templateFilesPath}`)
return { success: false }
return {
reason: `Could not find template source files from ${templateFilesPath}`,
success: false,
}
} else {
if (debug) logDebug('Found template source files')
}
const userAppDir = path.resolve(projectDir, 'src/app')
// src/app or app
const userAppDirGlob = await globby(['**/app'], {
cwd: projectDir,
onlyDirectories: true,
})
const userAppDir = path.resolve(projectDir, userAppDirGlob?.[0])
if (!fs.existsSync(userAppDir)) {
error(`Could not find user app directory at ${userAppDir}`)
return { success: false }
return { reason: `Could not find user app directory inside ${projectDir}`, success: false }
} else {
logDebug(`Found user app directory: ${userAppDir}`)
}
@@ -65,5 +137,83 @@ export async function initNext(
logDebug(`Copying template files from ${templateFilesPath} to ${userAppDir}`)
copyRecursiveSync(templateFilesPath, userAppDir, debug)
success('Successfully initialized.')
return { success: true }
return { success: true, userAppDir }
}
async function installDeps(projectDir: string) {
const packageManager = await detect({ cwd: projectDir })
if (!packageManager) {
throw new Error('Could not detect package manager')
}
info(`Installing dependencies with ${packageManager}`, 1)
const packagesToInstall = [
'payload',
'@payloadcms/db-mongodb',
'@payloadcms/next',
'@payloadcms/richtext-slate',
'@payloadcms/ui',
].map((pkg) => `${pkg}@alpha`)
let exitCode = 0
switch (packageManager) {
case 'npm': {
;({ exitCode } = await execa('npm', ['install', '--save', ...packagesToInstall], {
cwd: projectDir,
}))
break
}
case 'yarn':
case 'pnpm': {
;({ exitCode } = await execa(packageManager, ['add', ...packagesToInstall], {
cwd: projectDir,
}))
break
}
case 'bun': {
warning('Bun support is untested.')
;({ exitCode } = await execa('bun', ['add', ...packagesToInstall], { cwd: projectDir }))
break
}
}
if (exitCode !== 0) {
error(`Failed to install dependencies with ${packageManager}`)
} else {
success(`Successfully installed dependencies`)
}
return { success: exitCode === 0 }
}
function findOrCreatePayloadConfig(projectDir: string) {
const configPath = path.resolve(projectDir, 'payload.config.ts')
if (fs.existsSync(configPath)) {
return { message: 'Found existing payload.config.ts', success: true }
} else {
// Create default config
// TODO: Pull this from templates
const defaultConfig = `import path from "path";
import { mongooseAdapter } from "@payloadcms/db-mongodb"; // database-adapter-import
import { slateEditor } from "@payloadcms/richtext-slate"; // editor-import
import { buildConfig } from "payload/config";
export default buildConfig({
editor: slateEditor({}), // editor-config
collections: [],
secret: "asdfasdf",
typescript: {
outputFile: path.resolve(__dirname, "payload-types.ts"),
},
graphQL: {
schemaOutputFile: path.resolve(__dirname, "generated-schema.graphql"),
},
db: mongooseAdapter({
url: "mongodb://localhost:27017/next-payload-3",
}),
});
`
fse.writeFileSync(configPath, defaultConfig)
return { message: 'Created default payload.config.ts', success: true }
}
}

View File

@@ -12,7 +12,7 @@ import { parseTemplate } from './lib/parse-template'
import { selectDb } from './lib/select-db'
import { getValidTemplates, validateTemplate } from './lib/templates'
import { writeEnvFile } from './lib/write-env-file'
import { success } from './utils/log'
import { error, success } from './utils/log'
import { helpMessage, successMessage, welcomeMessage } from './utils/messages'
export class Main {
@@ -61,6 +61,11 @@ export class Main {
if (this.args['--init-next']) {
const result = await initNext(this.args)
if (!result.success) {
error(result.reason || 'Failed to initialize Payload app in Next.js project')
} else {
success('Payload app successfully initialized in Next.js project')
}
process.exit(result.success ? 0 : 1)
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "3.0.0-alpha.11",
"version": "3.0.0-alpha.18",
"description": "The officially supported MongoDB database adapter for Payload - Update 2",
"repository": "https://github.com/payloadcms/payload",
"license": "MIT",

View File

@@ -6,7 +6,14 @@ import mongoose from 'mongoose'
import type { MongooseAdapter } from '.'
export const connect: Connect = async function connect(this: MongooseAdapter) {
export const connect: Connect = async function connect(
this: MongooseAdapter,
options = {
hotReload: false,
},
) {
const { hotReload } = options
if (this.url === false) {
return
}
@@ -24,6 +31,8 @@ export const connect: Connect = async function connect(this: MongooseAdapter) {
useFacet: undefined,
}
if (hotReload) connectionOptions.autoIndex = false
try {
this.connection = (await mongoose.connect(urlToConnect, connectionOptions)).connection
@@ -34,12 +43,15 @@ export const connect: Connect = async function connect(this: MongooseAdapter) {
this.beginTransaction = undefined
}
if (process.env.PAYLOAD_DROP_DATABASE === 'true') {
this.payload.logger.info('---- DROPPING DATABASE ----')
await mongoose.connection.dropDatabase()
this.payload.logger.info('---- DROPPED DATABASE ----')
if (!hotReload) {
if (process.env.PAYLOAD_DROP_DATABASE === 'true') {
this.payload.logger.info('---- DROPPING DATABASE ----')
await mongoose.connection.dropDatabase()
this.payload.logger.info('---- DROPPED DATABASE ----')
}
this.payload.logger.info(successfulConnectionMessage)
}
this.payload.logger.info(successfulConnectionMessage)
} catch (err) {
this.payload.logger.error(`Error: cannot connect to MongoDB. Details: ${err.message}`, err)
process.exit(1)

View File

@@ -10,6 +10,7 @@ export const destroy: Destroy = async function destroy(this: MongooseAdapter) {
await mongoose.connection.close()
await this.mongoMemoryServer.stop()
} else {
await mongoose.connection.close()
await mongoose.disconnect()
}
Object.keys(mongoose.models).map((model) => mongoose.deleteModel(model))
}

View File

@@ -19,7 +19,7 @@ import { buildGlobalModel } from './models/buildGlobalModel'
import buildSchema from './models/buildSchema'
import getBuildQueryPlugin from './queries/buildQuery'
export const init: Init = async function init(this: MongooseAdapter) {
export const init: Init = function init(this: MongooseAdapter) {
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
const schema = buildCollectionSchema(collection, this.payload.config)

View File

@@ -1,8 +1,7 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable class-methods-use-this */
/* eslint-disable @typescript-eslint/no-use-before-define */
/* eslint-disable no-use-before-define */
import type { IndexOptions, SchemaOptions, SchemaTypeOptions } from 'mongoose'
import type { IndexOptions, Schema, SchemaOptions, SchemaTypeOptions } from 'mongoose'
import type { SanitizedConfig, SanitizedLocalizationConfig } from 'payload/config'
import type {
ArrayField,
@@ -32,7 +31,7 @@ import type {
UploadField,
} from 'payload/types'
import { Schema } from 'mongoose'
import mongoose from 'mongoose'
import {
fieldAffectsData,
fieldIsLocalized,
@@ -126,7 +125,7 @@ const buildSchema = (
}
}
const schema = new Schema(fields, options)
const schema = new mongoose.Schema(fields, options)
schemaFields.forEach((field) => {
if (!fieldIsPresentationalOnly(field)) {
@@ -176,7 +175,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
buildSchemaOptions: BuildSchemaOptions,
): void => {
const fieldSchema = {
type: [new Schema({}, { _id: false, discriminatorKey: 'blockType' })],
type: [new mongoose.Schema({}, { _id: false, discriminatorKey: 'blockType' })],
default: undefined,
}
@@ -185,7 +184,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
})
field.blocks.forEach((blockItem: Block) => {
const blockSchema = new Schema({}, { _id: false, id: false })
const blockSchema = new mongoose.Schema({}, { _id: false, id: false })
blockItem.fields.forEach((blockField) => {
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[blockField.type]
@@ -307,7 +306,10 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
config: SanitizedConfig,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: Schema.Types.Mixed }
const baseSchema = {
...formatBaseSchema(field, buildSchemaOptions),
type: mongoose.Schema.Types.Mixed,
}
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
@@ -405,17 +407,17 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
localeSchema = {
...formatBaseSchema(field, buildSchemaOptions),
_id: false,
type: Schema.Types.Mixed,
type: mongoose.Schema.Types.Mixed,
relationTo: { type: String, enum: field.relationTo },
value: {
type: Schema.Types.Mixed,
type: mongoose.Schema.Types.Mixed,
refPath: `${field.name}.${locale}.relationTo`,
},
}
} else {
localeSchema = {
...formatBaseSchema(field, buildSchemaOptions),
type: Schema.Types.Mixed,
type: mongoose.Schema.Types.Mixed,
ref: field.relationTo,
}
}
@@ -431,10 +433,10 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
schemaToReturn = {
...formatBaseSchema(field, buildSchemaOptions),
_id: false,
type: Schema.Types.Mixed,
type: mongoose.Schema.Types.Mixed,
relationTo: { type: String, enum: field.relationTo },
value: {
type: Schema.Types.Mixed,
type: mongoose.Schema.Types.Mixed,
refPath: `${field.name}.relationTo`,
},
}
@@ -448,7 +450,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
} else {
schemaToReturn = {
...formatBaseSchema(field, buildSchemaOptions),
type: Schema.Types.Mixed,
type: mongoose.Schema.Types.Mixed,
ref: field.relationTo,
}
@@ -470,7 +472,10 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
config: SanitizedConfig,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: Schema.Types.Mixed }
const baseSchema = {
...formatBaseSchema(field, buildSchemaOptions),
type: mongoose.Schema.Types.Mixed,
}
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
@@ -586,7 +591,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
): void => {
const baseSchema = {
...formatBaseSchema(field, buildSchemaOptions),
type: Schema.Types.Mixed,
type: mongoose.Schema.Types.Mixed,
ref: field.relationTo,
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "0.7.0",
"version": "3.0.0-alpha.18",
"description": "The officially supported Postgres database adapter for Payload",
"repository": "https://github.com/payloadcms/payload",
"license": "MIT",

View File

@@ -46,7 +46,14 @@ const connectWithReconnect = async function ({
})
}
export const connect: Connect = async function connect(this: PostgresAdapter, payload) {
export const connect: Connect = async function connect(
this: PostgresAdapter,
options = {
hotReload: false,
},
) {
const { hotReload } = options
this.schema = {
...this.tables,
...this.relations,
@@ -55,23 +62,26 @@ export const connect: Connect = async function connect(this: PostgresAdapter, pa
try {
this.pool = new Pool(this.poolOptions)
await connectWithReconnect({ adapter: this, payload })
await connectWithReconnect({ adapter: this, payload: this.payload })
const logger = this.logger || false
this.drizzle = drizzle(this.pool, { logger, schema: this.schema })
if (process.env.PAYLOAD_DROP_DATABASE === 'true') {
this.payload.logger.info(`---- DROPPING TABLES SCHEMA(${this.schemaName || 'public'}) ----`)
await this.drizzle.execute(
sql.raw(`
drop schema if exists ${this.schemaName || 'public'} cascade;
create schema ${this.schemaName || 'public'};
`),
)
this.payload.logger.info('---- DROPPED TABLES ----')
if (!hotReload) {
if (process.env.PAYLOAD_DROP_DATABASE === 'true') {
this.payload.logger.info(`---- DROPPING TABLES SCHEMA(${this.schemaName || 'public'}) ----`)
await this.drizzle.execute(
sql.raw(`
drop schema if exists ${this.schemaName || 'public'} cascade;
create schema ${this.schemaName || 'public'};
`),
)
this.payload.logger.info('---- DROPPED TABLES ----')
}
}
} catch (err) {
payload.logger.error(`Error: cannot connect to Postgres. Details: ${err.message}`, err)
this.payload.logger.error(`Error: cannot connect to Postgres. Details: ${err.message}`, err)
process.exit(1)
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.0.0-alpha.11",
"version": "3.0.0-alpha.18",
"main": "./src/index.ts",
"types": "./src/index.d.ts",
"scripts": {

View File

@@ -10,6 +10,6 @@
}
},
"module": {
"type": "commonjs"
"type": "es6"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.0.0-alpha.11",
"version": "3.0.0-alpha.18",
"main": "./src/index.ts",
"types": "./src/index.d.ts",
"bin": {
@@ -8,7 +8,7 @@
},
"scripts": {
"build:webpack": "webpack --config webpack.config.js",
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:webpack && pnpm build:types",
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:types",
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
"build:types": "tsc --emitDeclarationOnly --outDir dist",
"clean": "rimraf {dist,*.tsbuildinfo}",
@@ -49,6 +49,7 @@
"@payloadcms/eslint-config": "workspace:*",
"@types/react": "18.2.15",
"@types/react-dom": "18.2.7",
"@types/ws": "^8.5.10",
"css-loader": "^6.10.0",
"css-minimizer-webpack-plugin": "^6.0.0",
"file-loader": "6.2.0",
@@ -77,7 +78,9 @@
"graphql-playground-html": "1.6.30",
"path-to-regexp": "^6.2.1",
"react-diff-viewer-continued": "3.2.6",
"react-toastify": "8.2.0"
"react-toastify": "8.2.0",
"sass": "^1.71.1",
"ws": "^8.16.0"
},
"peerDependencies": {
"http-status": "1.6.2",

View File

@@ -1 +1 @@
export { withPayload } from '../withPayload'
export { default as withPayload } from '../withPayload'

View File

@@ -1,6 +1,5 @@
export { AdminLayout } from './layouts/Admin'
export { DocumentLayout } from './layouts/Document'
export { RootLayout } from './layouts/Root'
export { Dashboard as DashboardPage, generateMetadata as dashboardMeta } from './views/Dashboard'
export { Login, generateMetadata as loginMeta } from './views/Login'
export { Dashboard as DashboardPage } from './views/Dashboard'
export { Login } from './views/Login'
export { RootPage } from './views/Root'

View File

@@ -1,42 +0,0 @@
import type { SanitizedConfig } from 'payload/types'
import { DocumentHeader } from '@payloadcms/ui'
import '@payloadcms/ui/scss/app.scss'
import React, { Fragment } from 'react'
import { initPage } from '../../utilities/initPage'
export const metadata = {
description: 'Generated by Next.js',
title: 'Next.js',
}
export const DocumentLayout = async ({
children,
collectionSlug,
config: configPromise,
globalSlug,
}: {
children: React.ReactNode
collectionSlug?: string
config: Promise<SanitizedConfig>
globalSlug?: string
}) => {
const { collectionConfig, globalConfig, req } = await initPage({
collectionSlug,
config: configPromise,
globalSlug,
})
return (
<Fragment>
<DocumentHeader
collectionConfig={collectionConfig}
config={req.payload.config}
globalConfig={globalConfig}
i18n={req.i18n}
/>
{children}
</Fragment>
)
}

View File

@@ -5,15 +5,17 @@ import { translations } from '@payloadcms/translations/client'
import { RootProvider, buildComponentMap } from '@payloadcms/ui'
import '@payloadcms/ui/scss/app.scss'
import { headers as getHeaders } from 'next/headers'
import { parseCookies } from 'payload/auth'
import { createClientConfig } from 'payload/config'
import { deepMerge } from 'payload/utilities'
import React from 'react'
import 'react-toastify/dist/ReactToastify.css'
import { getRequestLanguage } from '../../utilities/getRequestLanguage'
import { DefaultEditView } from '../../views/Edit/Default'
import { DefaultListView } from '../../views/List/Default'
import { DefaultCell } from '../../views/List/Default/Cell'
import { getRequestLanguage } from '../../utilities/getRequestLanguage'
import { getPayload } from '../../utilities/getPayload'
export const metadata = {
description: 'Generated by Next.js',
@@ -34,10 +36,13 @@ export const RootLayout = async ({
const headers = getHeaders()
const { cookies, user } = await auth({
config: configPromise,
const payload = await getPayload({ config: configPromise })
const { cookies, user, permissions } = await auth({
payload,
headers,
})
const lang =
getRequestLanguage({
cookies,
@@ -58,6 +63,7 @@ export const RootLayout = async ({
DefaultEditView,
DefaultListView,
config,
permissions: permissions,
})
return (

View File

@@ -1,6 +1,6 @@
import fs from 'fs'
function iteratorToStream(iterator) {
export function iteratorToStream(iterator) {
return new ReadableStream({
async pull(controller) {
const { done, value } = await iterator.next()
@@ -13,7 +13,7 @@ function iteratorToStream(iterator) {
})
}
async function* nodeStreamToIterator(stream: fs.ReadStream) {
export async function* nodeStreamToIterator(stream: fs.ReadStream) {
for await (const chunk of stream) {
yield new Uint8Array(chunk)
}

View File

@@ -51,6 +51,10 @@ if (!cached) {
}
export const getGraphql = async (config: Promise<SanitizedConfig> | SanitizedConfig) => {
if (process.env.NODE_ENV === 'development') {
cached = global._payload_graphql = { graphql: null, promise: null }
}
if (cached.graphql) {
return cached.graphql
}
@@ -110,7 +114,7 @@ export const POST =
}
return response
},
schema: schema,
schema,
validationRules: (request, args, defaultRules) => defaultRules.concat(validationRules(args)),
})(originalRequest)

View File

@@ -77,6 +77,7 @@ export const GET =
params: { collection: collectionSlug },
request,
})
collection = req.payload.collections?.[collectionSlug]
if (!collection) {
@@ -90,7 +91,7 @@ export const GET =
)
}
if (collection.config.upload.disableLocalStorage) {
if (collection.config.upload.disableLocalStorage && !collection.config.upload.handlers) {
throw new APIError(
`This collection has local storage disabled: ${collectionSlug}`,
httpStatus.BAD_REQUEST,
@@ -103,10 +104,21 @@ export const GET =
req,
})
let response: Response = null
if (collection.config.upload.handlers?.length) {
for (const handler of collection.config.upload.handlers) {
response = await handler(req, { params })
}
return response
}
const fileDir = collection.config.upload?.staticDir || collection.config.slug
const filePath = path.resolve(`${fileDir}/${filename}`)
const stats = await fsPromises.stat(filePath)
const data = streamFile(filePath)
return new Response(data, {
headers: new Headers({
'content-length': stats.size + '',

View File

@@ -12,7 +12,7 @@ if (!cached) {
}
export const getFieldSchemaMap = (config: SanitizedConfig): FieldSchemaMap => {
if (cached) {
if (cached && process.env.NODE_ENV !== 'development') {
return cached
}

View File

@@ -9,7 +9,6 @@ import type { CollectionRouteHandler } from '../types'
export const find: CollectionRouteHandler = async ({ collection, req }) => {
const { searchParams } = req
// parse using `qs` to handle `where` queries
const { depth, draft, limit, page, sort, where } = qs.parse(searchParams.toString()) as {
depth?: string

View File

@@ -208,6 +208,7 @@ export const GET =
if (`doc-${slug2}-by-id` in endpoints.collection.GET) {
// /:collection/access/:id
// /:collection/versions/:id
res = await (
endpoints.collection.GET[`doc-${slug2}-by-id`] as CollectionRouteHandlerWithID
)({ id: slug3, collection, req })
@@ -334,6 +335,7 @@ export const POST =
// /:collection/forgot-password
// /:collection/reset-password
// /:collection/refresh-token
res = await (endpoints.collection.POST?.[slug2] as CollectionRouteHandler)({
collection,
req,

View File

@@ -1,19 +1,12 @@
import type { PayloadRequest, SanitizedConfig } from 'payload/types'
import type { Payload, PayloadRequest } from 'payload/types'
import { getPayload } from 'payload'
import { getAccessResults, getAuthenticatedUser, parseCookies } from 'payload/auth'
import { cache } from 'react'
export const auth = cache(
async ({
config,
headers,
}: {
config: Promise<SanitizedConfig> | SanitizedConfig
headers: Request['headers']
}) => {
async ({ headers, payload }: { headers: Request['headers']; payload: Payload }) => {
const cookies = parseCookies(headers)
const payload = await getPayload({ config })
const user = await getAuthenticatedUser({
cookies,
headers,

View File

@@ -1,117 +0,0 @@
import type { ClientConfig, Field, SanitizedConfig } from 'payload/types'
export const sanitizeField = (f) => {
const field = { ...f }
if ('access' in field) delete field.access
if ('hooks' in field) delete field.hooks
if ('validate' in field) delete field.validate
if ('defaultValue' in field) delete field.defaultValue
if ('label' in field) delete field.label
if ('fields' in field) {
field.fields = sanitizeFields(field.fields)
}
if ('editor' in field) {
delete field.editor
}
if ('blocks' in field) {
field.blocks = field.blocks.map((block) => {
const sanitized = { ...block }
sanitized.fields = sanitizeFields(sanitized.fields)
return sanitized
})
}
if ('tabs' in field) {
field.tabs = field.tabs.map((tab) => sanitizeField(tab))
}
if ('admin' in field) {
field.admin = { ...field.admin }
if ('components' in field.admin) {
delete field.admin.components
}
if ('condition' in field.admin) {
delete field.admin.condition
}
if ('description' in field.admin) {
delete field.admin.description
}
}
return field
}
const sanitizeCollections = (
collections: SanitizedConfig['collections'],
): ClientConfig['collections'] =>
collections.map((collection) => {
const sanitized = { ...collection }
sanitized.fields = sanitizeFields(sanitized.fields)
delete sanitized.hooks
delete sanitized.access
delete sanitized.endpoints
if ('editor' in sanitized) delete sanitized.editor
if ('admin' in sanitized) {
sanitized.admin = { ...sanitized.admin }
if ('components' in sanitized.admin) {
delete sanitized.admin.components
}
}
return sanitized
})
const sanitizeGlobals = (globals: SanitizedConfig['globals']): ClientConfig['globals'] =>
globals.map((global) => {
const sanitized = { ...global }
sanitized.fields = sanitizeFields(sanitized.fields)
delete sanitized.hooks
delete sanitized.access
delete sanitized.endpoints
if ('admin' in sanitized) {
sanitized.admin = { ...sanitized.admin }
if ('components' in sanitized.admin) {
delete sanitized.admin.components
}
}
return sanitized
})
export const sanitizeFields = (fields: Field[]): Field[] => fields.map(sanitizeField)
export const createClientConfig = async (
configPromise: Promise<SanitizedConfig> | SanitizedConfig,
): Promise<ClientConfig> => {
const config = await configPromise
const clientConfig = { ...config }
delete clientConfig.endpoints
delete clientConfig.db
delete clientConfig.editor
'localization' in clientConfig &&
clientConfig.localization &&
clientConfig.localization.locales.forEach((locale) => {
delete locale.toString
})
clientConfig.onInit = undefined
clientConfig.collections = sanitizeCollections(clientConfig.collections)
clientConfig.globals = sanitizeGlobals(clientConfig.globals)
return clientConfig
}

View File

@@ -7,13 +7,13 @@ import type {
import { initI18n } from '@payloadcms/translations'
import { translations } from '@payloadcms/translations/api'
import { getPayload } from 'payload'
import { getAuthenticatedUser } from 'payload/auth'
import { parseCookies } from 'payload/auth'
import { getDataLoader } from 'payload/utilities'
import { URL } from 'url'
import { getDataAndFile } from './getDataAndFile'
import { getPayload } from './getPayload'
import { getRequestLanguage } from './getRequestLanguage'
import { getRequestLocales } from './getRequestLocales'
@@ -32,6 +32,7 @@ export const createPayloadRequest = async ({
}: Args): Promise<PayloadRequest> => {
const cookies = parseCookies(request.headers)
const payload = await getPayload({ config: configPromise })
const { collections, config } = payload
let collection: Collection = undefined

View File

@@ -0,0 +1,71 @@
import type { GeneratedTypes, Payload } from 'payload'
import type { InitOptions } from 'payload/config'
import { BasePayload } from 'payload'
import WebSocket from 'ws'
let cached = global._payload
if (!cached) {
// eslint-disable-next-line no-multi-assign
cached = global._payload = { payload: null, promise: null }
}
export const getPayload = async (options: InitOptions): Promise<Payload> => {
if (cached.payload) {
const config = await options.config
if (cached.reload) {
cached.reload = false
if (typeof cached.payload.db.destroy === 'function') {
await cached.payload.db.destroy()
}
cached.payload.config = config
cached.payload.collections = config.collections.reduce((collections, collection) => {
collections[collection.slug] = { config: collection }
return collections
}, {})
// TODO: re-build payload.globals as well as any other properties
// that may change on Payload singleton
await cached.payload.db.init()
await cached.payload.db.connect({ hotReload: true })
}
return cached.payload
}
if (!cached.promise) {
cached.promise = new BasePayload<GeneratedTypes>().init(options)
}
try {
cached.payload = await cached.promise
if (process.env.NODE_ENV !== 'production') {
try {
const ws = new WebSocket('ws://localhost:3000/_next/webpack-hmr')
ws.onmessage = (event) => {
if (typeof event.data === 'string') {
const data = JSON.parse(event.data)
if ('action' in data && data.action === 'serverComponentChanges') {
cached.reload = true
}
}
}
} catch (_) {
// swallow e
}
}
} catch (e) {
cached.promise = null
throw e
}
return cached.payload
}

View File

@@ -1,6 +1,5 @@
import type { Permissions } from 'payload/auth'
import type { Locale } from 'payload/config'
import type {
InitPageResult,
PayloadRequest,
SanitizedCollectionConfig,
SanitizedConfig,
@@ -12,69 +11,59 @@ import { translations } from '@payloadcms/translations/client'
import { findLocaleFromCode } from '@payloadcms/ui'
import { headers as getHeaders } from 'next/headers'
import { redirect } from 'next/navigation'
import { getPayload } from 'payload'
import { createLocalReq } from 'payload/utilities'
import qs from 'qs'
import { getPayload } from '../utilities/getPayload'
import { auth } from './auth'
import { getRequestLanguage } from './getRequestLanguage'
type Args = {
collectionSlug?: string
config: Promise<SanitizedConfig> | SanitizedConfig
globalSlug?: string
localeParam?: string
redirectUnauthenticatedUser?: boolean
route?: string
searchParams?: { [key: string]: string | string[] | undefined }
}
export type InitPageResult = {
collectionConfig?: SanitizedCollectionConfig
globalConfig?: SanitizedGlobalConfig
locale: Locale
permissions: Permissions
req: PayloadRequest
}
export const initPage = async ({
config: configPromise,
localeParam,
redirectUnauthenticatedUser = false,
route,
searchParams,
}: Args): Promise<InitPageResult> => {
const headers = getHeaders()
const localeParam = searchParams?.locale as string
const payload = await getPayload({ config: configPromise })
const { cookies, permissions, user } = await auth({
config: configPromise,
headers,
payload,
})
const config = await configPromise
const routeSegments = route.replace(config.routes.admin, '').split('/').filter(Boolean)
const routeSegments = route.replace(payload.config.routes.admin, '').split('/').filter(Boolean)
const collectionSlug = routeSegments[0] === 'collections' ? routeSegments[1] : undefined
const globalSlug = routeSegments[0] === 'globals' ? routeSegments[1] : undefined
const { collections, globals, localization, routes } = config
const { collections, globals, localization, routes } = payload.config
if (redirectUnauthenticatedUser && !user && route !== '/login') {
if ('redirect' in searchParams) delete searchParams.redirect
const stringifiedSearchParams = Object.keys(searchParams ?? {}).length
? `?${qs.stringify(searchParams)}`
: ''
redirect(`${routes.admin}/login?redirect=${routes.admin + route + stringifiedSearchParams}`)
redirect(`${routes.admin}/login?redirect=${route + stringifiedSearchParams}`)
}
const payload = await getPayload({ config })
const defaultLocale =
localization && localization.defaultLocale ? localization.defaultLocale : 'en'
const localeCode = localeParam || defaultLocale
const locale = localization && findLocaleFromCode(localization, localeCode)
const language = getRequestLanguage({ cookies, headers })
const i18n = initI18n({
config: config.i18n,
config: payload.config.i18n,
context: 'client',
language,
translations,
@@ -105,9 +94,11 @@ export const initPage = async ({
return {
collectionConfig,
cookies,
globalConfig,
locale,
permissions,
req,
translations: i18n.translations,
}
}

View File

@@ -0,0 +1,5 @@
export const timestamp = (label: string) => {
if (!process.env.PAYLOAD_TIME) process.env.PAYLOAD_TIME = String(new Date().getTime())
const now = new Date()
console.log(`[${now.getTime() - Number(process.env.PAYLOAD_TIME)}ms] ${label}`)
}

View File

@@ -14,9 +14,9 @@ const chars = {
const baseClass = 'query-inspector'
const Bracket = ({
type,
comma = false,
position,
type,
}: {
comma?: boolean
position: 'end' | 'start'
@@ -49,7 +49,7 @@ export const RenderJSON = ({
parentType = 'object',
trailingComma = false,
}: Args) => {
const objectKeys = Object.keys(object)
const objectKeys = object ? Object.keys(object) : []
const objectLength = objectKeys.length
const [isOpen, setIsOpen] = React.useState<boolean>(true)

View File

@@ -1,5 +1,6 @@
'use client'
import type { EditViewProps } from 'payload/config'
import type { EditViewProps } from 'payload/types'
import {
Checkbox,

View File

@@ -1,6 +1,6 @@
import React from 'react'
import type { ServerSideEditViewProps } from 'payload/types'
import type { ServerSideEditViewProps } from '../Edit/types'
import React from 'react'
import { sanitizeEditViewProps } from '../Edit/sanitizeEditViewProps'
import { APIViewClient } from './index.client'

View File

@@ -1,5 +1,4 @@
import type { Metadata } from 'next'
import type { Data, DocumentPreferences, SanitizedConfig } from 'payload/types'
import type { Data, DocumentPreferences, ServerSideEditViewProps } from 'payload/types'
import {
HydrateClientUser,
@@ -11,47 +10,24 @@ import {
import { notFound } from 'next/navigation'
import React, { Fragment } from 'react'
import type { InitPageResult } from '../../utilities/initPage'
import type { ServerSideEditViewProps } from '../Edit/types'
import type { AdminViewProps } from '../Root'
import { getNextI18n } from '../../utilities/getNextI18n'
import { meta } from '../../utilities/meta'
import { EditView } from '../Edit'
import { Settings } from './Settings'
export const generateMetadata = async ({
config: configPromise,
}: {
config: Promise<SanitizedConfig>
}): Promise<Metadata> => {
const config = await configPromise
const { t } = await getNextI18n({
config,
})
return meta({
config,
description: `${t('authentication:accountOfCurrentUser')}`,
keywords: `${t('authentication:account')}`,
title: t('authentication:account'),
})
}
export const Account = async ({
page,
searchParams,
}: {
page: InitPageResult
searchParams: { [key: string]: string | string[] | undefined }
}) => {
const { locale, permissions, req } = page
export { generateAccountMetadata } from './meta'
export const Account: React.FC<AdminViewProps> = async ({ initPageResult, searchParams }) => {
const {
payload,
payload: { config },
user,
} = req
locale,
permissions,
req: {
payload,
payload: { config },
user,
},
req,
} = initPageResult
const {
admin: { components: { views: { Account: CustomAccountComponent } = {} } = {}, user: userSlug },
@@ -112,19 +88,14 @@ export const Account = async ({
action: `${serverURL}${api}/${userSlug}/${data?.id}?locale=${locale.code}`,
apiURL: `${serverURL}${api}/${userSlug}/${data?.id}?locale=${locale.code}`,
collectionSlug: userSlug,
config,
data,
docPermissions: collectionPermissions,
docPreferences,
hasSavePermission: collectionPermissions?.update?.permission,
i18n: req.i18n,
initPageResult,
initialState,
locale,
payload,
permissions,
searchParams,
updatedAt: '', // TODO
user,
}
return (

View File

@@ -0,0 +1,11 @@
import { meta } from '../../utilities/meta'
import { GenerateViewMetadata } from '../Root'
export const generateAccountMetadata: GenerateViewMetadata = async ({ config, i18n: { t } }) => {
return meta({
config,
description: `${t('authentication:accountOfCurrentUser')}`,
keywords: `${t('authentication:account')}`,
title: t('authentication:account'),
})
}

View File

@@ -1,13 +1,19 @@
'use client'
import { RenderFields, useComponentMap } from '@payloadcms/ui'
import { FieldMap, RenderFields, useComponentMap } from '@payloadcms/ui'
import React from 'react'
export const CreateFirstUserFields: React.FC<{
userSlug: string
}> = ({ userSlug }) => {
createFirstUserFieldMap: FieldMap
}> = ({ userSlug, createFirstUserFieldMap }) => {
const { getFieldMap } = useComponentMap()
const fieldMap = getFieldMap({ collectionSlug: userSlug })
return <RenderFields fieldMap={fieldMap} />
return (
<RenderFields
fieldMap={[...(fieldMap || []), ...(createFirstUserFieldMap || [])]}
operation="create"
/>
)
}

View File

@@ -1,66 +1,31 @@
import type { Metadata } from 'next'
import type { Field } from 'payload/types'
import type { SanitizedConfig } from 'payload/types'
import { Form, FormSubmit, buildStateFromSchema } from '@payloadcms/ui'
import { redirect } from 'next/navigation'
import { Form, FormSubmit, buildStateFromSchema, mapFields } from '@payloadcms/ui'
import React from 'react'
import type { InitPageResult } from '../../utilities/initPage'
import type { AdminViewProps } from '../Root'
import { getNextI18n } from '../../utilities/getNextI18n'
import { meta } from '../../utilities/meta'
import { CreateFirstUserFields } from './index.client'
import './index.scss'
export const generateMetadata = async ({
config: configPromise,
}: {
config: Promise<SanitizedConfig>
}): Promise<Metadata> => {
const config = await configPromise
export { generateCreateFirstUserMetadata } from './meta'
const { t } = await getNextI18n({
config,
})
return meta({
config,
description: t('authentication:createFirstUser'),
keywords: t('general:create'),
title: t('authentication:createFirstUser'),
})
}
type Props = {
page: InitPageResult
params: { [key: string]: string | string[] }
searchParams: { [key: string]: string | string[] }
}
export const CreateFirstUser: React.FC<Props> = async ({ page }) => {
const { req } = page
const { config } = req.payload
export const CreateFirstUser: React.FC<AdminViewProps> = async ({ initPageResult }) => {
const {
admin: { user: userSlug },
routes: { admin: adminRoute, api: apiRoute },
serverURL,
} = config
req,
req: {
payload: {
config,
config: {
admin: { user: userSlug },
routes: { admin: adminRoute, api: apiRoute },
serverURL,
},
},
},
} = initPageResult
if (req.user) {
redirect(adminRoute)
}
const { docs } = await req.payload.find({
collection: userSlug,
depth: 0,
limit: 1,
})
if (docs.length > 0) {
redirect(adminRoute)
}
const fields = [
const fields: Field[] = [
{
name: 'email',
type: 'email',
@@ -69,17 +34,23 @@ export const CreateFirstUser: React.FC<Props> = async ({ page }) => {
},
{
name: 'password',
type: 'password',
type: 'text',
label: req.t('general:password'),
required: true,
},
{
name: 'confirm-password',
type: 'confirmPassword',
type: 'text',
label: req.t('authentication:confirmPassword'),
required: true,
},
] as Field[]
]
const createFirstUserFieldMap = mapFields({
fieldSchema: fields,
config,
parentPath: userSlug,
})
const formState = await buildStateFromSchema({
fieldSchema: fields,
@@ -99,7 +70,10 @@ export const CreateFirstUser: React.FC<Props> = async ({ page }) => {
redirect={adminRoute}
validationOperation="create"
>
<CreateFirstUserFields userSlug={userSlug} />
<CreateFirstUserFields
userSlug={userSlug}
createFirstUserFieldMap={createFirstUserFieldMap}
/>
<FormSubmit>{req.t('general:create')}</FormSubmit>
</Form>
</React.Fragment>

View File

@@ -0,0 +1,14 @@
import { meta } from '../../utilities/meta'
import { GenerateViewMetadata } from '../Root'
export const generateCreateFirstUserMetadata: GenerateViewMetadata = async ({
config,
i18n: { t },
}) => {
return meta({
config,
description: t('authentication:createFirstUser'),
keywords: t('general:create'),
title: t('authentication:createFirstUser'),
})
}

View File

@@ -15,6 +15,7 @@ import {
import React, { Fragment, useEffect, useState } from 'react'
import './index.scss'
import { Permissions } from 'payload/auth'
const baseClass = 'dashboard'
@@ -22,7 +23,8 @@ export const DefaultDashboardClient: React.FC<{
Link: React.ComponentType
visibleCollections: string[]
visibleGlobals: string[]
}> = ({ Link, visibleCollections, visibleGlobals }) => {
permissions: Permissions
}> = ({ Link, visibleCollections, visibleGlobals, permissions }) => {
const config = useConfig()
const {
@@ -31,7 +33,7 @@ export const DefaultDashboardClient: React.FC<{
routes: { admin },
} = config
const { permissions, user } = useAuth()
const { user } = useAuth()
const { i18n, t } = useTranslation()

View File

@@ -5,6 +5,7 @@ import React from 'react'
import { DefaultDashboardClient } from './index.client'
import './index.scss'
import { Permissions } from 'payload/auth'
const baseClass = 'dashboard'
@@ -13,6 +14,7 @@ export type DashboardProps = {
config: SanitizedConfig
visibleCollections: string[]
visibleGlobals: string[]
permissions: Permissions
}
export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
@@ -25,6 +27,7 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
},
visibleCollections,
visibleGlobals,
permissions,
} = props
return (
@@ -38,6 +41,7 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
Link={Link}
visibleCollections={visibleCollections}
visibleGlobals={visibleGlobals}
permissions={permissions}
/>
{Array.isArray(afterDashboard) &&
afterDashboard.map((Component, i) => <Component key={i} />)}

View File

@@ -1,49 +1,26 @@
import type { Metadata } from 'next'
import type { SanitizedConfig } from 'payload/types'
import { HydrateClientUser, RenderCustomComponent } from '@payloadcms/ui'
import Link from 'next/link'
import { isEntityHidden } from 'payload/utilities'
import React, { Fragment } from 'react'
import type { InitPageResult } from '../../utilities/initPage'
import type { AdminViewProps } from '../Root'
import type { DashboardProps } from './Default'
import { getNextI18n } from '../../utilities/getNextI18n'
import { meta } from '../../utilities/meta'
import { DefaultDashboard } from './Default'
export const generateMetadata = async ({
config: configPromise,
}: {
config: Promise<SanitizedConfig>
}): Promise<Metadata> => {
const config = await configPromise
const { t } = await getNextI18n({
config,
})
return meta({
config,
description: `${t('general:dashboard')} Payload`,
keywords: `${t('general:dashboard')}, Payload`,
title: t('general:dashboard'),
})
}
type Args = {
page: InitPageResult
searchParams: { [key: string]: string | string[] | undefined }
}
export const Dashboard = async ({ page, searchParams }: Args) => {
const { permissions, req } = page
export { generateDashboardMetadata } from './meta'
export const Dashboard: React.FC<AdminViewProps> = ({
initPageResult,
// searchParams,
}) => {
const {
payload: { config },
user,
} = req
permissions,
req: {
payload: { config },
user,
},
} = initPageResult
const CustomDashboardComponent = config.admin.components?.views?.Dashboard
@@ -66,6 +43,7 @@ export const Dashboard = async ({ page, searchParams }: Args) => {
config,
visibleCollections,
visibleGlobals,
permissions,
}
return (

View File

@@ -0,0 +1,11 @@
import { meta } from '../../utilities/meta'
import { GenerateViewMetadata } from '../Root'
export const generateDashboardMetadata: GenerateViewMetadata = async ({ config, i18n: { t } }) => {
return meta({
config,
description: `${t('general:dashboard')} Payload`,
keywords: `${t('general:dashboard')}, Payload`,
title: t('general:dashboard'),
})
}

View File

@@ -1,4 +1,4 @@
import type { AdminViewComponent } from 'payload/config'
import type { EditViewComponent } from 'payload/config'
import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload/types'
export const getCustomViewByKey = (
@@ -6,7 +6,7 @@ export const getCustomViewByKey = (
| SanitizedCollectionConfig['admin']['components']['views']
| SanitizedGlobalConfig['admin']['components']['views'],
customViewKey: string,
): AdminViewComponent => {
): EditViewComponent => {
return typeof views?.Edit === 'function'
? views?.Edit
: typeof views?.Edit === 'object' &&

View File

@@ -1,4 +1,4 @@
import type { AdminViewComponent } from 'payload/config'
import type { EditViewComponent } from 'payload/config'
import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload/types'
export const getCustomViewByPath = (
@@ -6,7 +6,7 @@ export const getCustomViewByPath = (
| SanitizedCollectionConfig['admin']['components']['views']
| SanitizedGlobalConfig['admin']['components']['views'],
path: string,
): AdminViewComponent => {
): EditViewComponent => {
if (typeof views?.Edit === 'object' && typeof views?.Edit !== 'function') {
const foundViewConfig = Object.entries(views.Edit).find(([, view]) => {
if (typeof view === 'object' && typeof view !== 'function' && 'path' in view) {

View File

@@ -1,92 +1,90 @@
import type { I18n } from '@payloadcms/translations'
import type { Metadata } from 'next'
import type {
SanitizedCollectionConfig,
SanitizedConfig,
SanitizedGlobalConfig,
} from 'payload/types'
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'
export type GenerateEditViewMetadata = (args: {
collectionConfig?: SanitizedCollectionConfig
config: SanitizedConfig
globalConfig?: SanitizedGlobalConfig
i18n: I18n
isEditing: boolean
}) => Promise<Metadata>
export type GenerateEditViewMetadata = (
args: Parameters<GenerateViewMetadata>[0] & {
collectionConfig?: SanitizedCollectionConfig | null
globalConfig?: SanitizedGlobalConfig | null
isEditing: boolean
},
) => Promise<Metadata>
export const getMetaBySegment = async ({
config: configPromise,
export const getMetaBySegment: GenerateEditViewMetadata = async ({
config,
params,
}: {
config: Promise<SanitizedConfig>
params: {
collection?: string
global?: string
segments: string[]
}
}): Promise<Metadata> => {
const config = await configPromise
collectionConfig,
globalConfig,
}) => {
const { segments } = params
let fn: GenerateEditViewMetadata | null = null
const isEditing = Boolean(
params.collection && params.segments?.length > 0 && params.segments[0] !== 'create',
)
const [segmentOne] = segments
const isCollection = segmentOne === 'collections'
const isGlobal = segmentOne === 'globals'
if (params.collection && params?.segments?.length) {
const isEditing = Boolean(isCollection && segments?.length > 2 && segments[2] !== 'create')
if (isCollection) {
// `/:id`
if (params.segments.length === 1) {
fn = await import('../Edit/meta.ts').then((mod) => mod.generateMetadata)
if (params.segments.length === 3) {
fn = editMeta
}
// `/:id/api`
if (params.segments.length === 2 && params.segments[1] === 'api') {
fn = await import('../API/meta.ts').then((mod) => mod.generateMetadata)
if (params.segments.length === 4 && params.segments[3] === 'api') {
fn = apiMeta
}
// `/:id/preview`
if (params.segments.length === 2 && params.segments[1] === 'preview') {
fn = await import('../LivePreview/meta.ts').then((mod) => mod.generateMetadata)
if (params.segments.length === 4 && params.segments[3] === 'preview') {
fn = livePreviewMeta
}
// `/:id/versions`
if (params.segments.length === 2 && params.segments[1] === 'versions') {
fn = await import('../Versions/meta.ts').then((mod) => mod.generateMetadata)
if (params.segments.length === 4 && params.segments[3] === 'versions') {
fn = versionsMeta
}
// `/:id/versions/:version`
if (params.segments.length === 3 && params.segments[1] === 'versions') {
fn = await import('../Version/meta.ts').then((mod) => mod.generateMetadata)
if (params.segments.length === 5 && params.segments[3] === 'versions') {
fn = versionMeta
}
}
if (params.global) {
if (isGlobal) {
// `/:slug`
if (!params.segments?.length) {
fn = await import('../Edit/meta.ts').then((mod) => mod.generateMetadata)
if (params.segments?.length === 2) {
fn = editMeta
}
// `/:slug/api`
if (params.segments?.length === 1 && params.segments[0] === 'api') {
fn = await import('../API/meta.ts').then((mod) => mod.generateMetadata)
if (params.segments?.length === 3 && params.segments[2] === 'api') {
fn = apiMeta
}
// `/:slug/preview`
if (params.segments?.length === 1 && params.segments[0] === 'preview') {
fn = await import('../LivePreview/meta.ts').then((mod) => mod.generateMetadata)
if (params.segments?.length === 3 && params.segments[2] === 'preview') {
fn = livePreviewMeta
}
// `/:slug/versions`
if (params.segments?.length === 1 && params.segments[0] === 'versions') {
fn = await import('../Versions/meta.ts').then((mod) => mod.generateMetadata)
if (params.segments?.length === 3 && params.segments[2] === 'versions') {
fn = versionsMeta
}
// `/:slug/versions/:version`
if (params.segments?.length === 2 && params.segments[0] === 'versions') {
fn = await import('../Version/meta.ts').then((mod) => mod.generateMetadata)
if (params.segments?.length === 4 && params.segments[2] === 'versions') {
fn = versionMeta
}
}
@@ -94,14 +92,6 @@ export const getMetaBySegment = async ({
config,
})
const collectionConfig = params.collection
? config?.collections?.find((collection) => collection.slug === params.collection)
: null
const globalConfig = params.global
? config?.globals?.find((global) => global.slug === params.global)
: null
if (typeof fn === 'function') {
return fn({
collectionConfig,

View File

@@ -1,5 +1,5 @@
import type { CollectionPermission, GlobalPermission, User } from 'payload/auth'
import type { AdminViewComponent } from 'payload/config'
import type { EditViewComponent } from 'payload/config'
import type {
SanitizedCollectionConfig,
SanitizedConfig,
@@ -31,13 +31,13 @@ export const getViewsFromConfig = async ({
routeSegments: string[]
user: User
}): Promise<{
CustomView: AdminViewComponent
DefaultView: AdminViewComponent
CustomView: EditViewComponent
DefaultView: EditViewComponent
} | null> => {
// Conditionally import and lazy load the default view
let DefaultView: AdminViewComponent = null
let CustomView: AdminViewComponent = null
const [entityType, entitySlug, createOrID, tabViewName, segmentFive] = routeSegments
let DefaultView: EditViewComponent = null
let CustomView: EditViewComponent = null
const views =
(collectionConfig && collectionConfig?.admin?.components?.views) ||
(globalConfig && globalConfig?.admin?.components?.views)
@@ -49,6 +49,9 @@ export const getViewsFromConfig = async ({
config?.admin?.livePreview?.globals?.includes(globalConfig?.slug)
if (collectionConfig) {
const [collectionEntity, collectionSlug, createOrID, nestedViewSlug, segmentFive] =
routeSegments
const {
admin: { hidden },
} = collectionConfig
@@ -58,7 +61,7 @@ export const getViewsFromConfig = async ({
}
// `../:id`, or `../create`
if (!tabViewName) {
if (!nestedViewSlug) {
switch (createOrID) {
case 'create': {
if ('create' in docPermissions && docPermissions?.create?.permission) {
@@ -77,10 +80,10 @@ export const getViewsFromConfig = async ({
}
}
if (tabViewName) {
if (nestedViewSlug) {
// `../:id/versions/:version`, etc
if (segmentFive) {
if (tabViewName === 'versions') {
if (nestedViewSlug === 'versions') {
if (docPermissions?.readVersions?.permission) {
CustomView = getCustomViewByKey(views, 'Version')
DefaultView = DefaultVersionView
@@ -90,7 +93,7 @@ export const getViewsFromConfig = async ({
// `../:id/api`, `../:id/preview`, `../:id/versions`, etc
if (routeSegments?.length === 4) {
switch (tabViewName) {
switch (nestedViewSlug) {
case 'api': {
if (collectionConfig?.admin?.hideAPIURL !== true) {
CustomView = getCustomViewByKey(views, 'API')
@@ -115,7 +118,7 @@ export const getViewsFromConfig = async ({
}
default: {
const path = `/${tabViewName}`
const path = `/${nestedViewSlug}`
CustomView = getCustomViewByPath(views, path)
break
}
@@ -125,6 +128,8 @@ export const getViewsFromConfig = async ({
}
if (globalConfig) {
const [globalEntity, globalSlug, nestedViewSlug] = routeSegments
const {
admin: { hidden },
} = globalConfig
@@ -133,14 +138,14 @@ export const getViewsFromConfig = async ({
return null
}
if (!routeSegments?.length) {
if (routeSegments?.length === 2) {
if (docPermissions?.read?.permission) {
CustomView = getCustomViewByKey(views, 'Default')
DefaultView = DefaultEditView
}
} else if (routeSegments?.length === 1) {
} else if (routeSegments?.length === 3) {
// `../:slug/api`, `../:slug/preview`, `../:slug/versions`, etc
switch (tabViewName) {
switch (nestedViewSlug) {
case 'api': {
if (globalConfig?.admin?.hideAPIURL !== true) {
CustomView = getCustomViewByKey(views, 'API')
@@ -174,7 +179,7 @@ export const getViewsFromConfig = async ({
}
} else if (routeSegments?.length === 2) {
// `../:slug/versions/:version`, etc
if (tabViewName === 'versions') {
if (nestedViewSlug === 'versions') {
if (docPermissions?.readVersions?.permission) {
CustomView = getCustomViewByKey(views, 'Version')
DefaultView = DefaultVersionView

View File

@@ -1,10 +1,11 @@
import type { QueryParamTypes } from '@payloadcms/ui'
import type { AdminViewComponent } from 'payload/config'
import type { EditViewComponent } from 'payload/config'
import type {
DocumentPreferences,
Document as DocumentType,
Field,
SanitizedConfig,
ServerSideEditViewProps,
} from 'payload/types'
import type { DocumentPermissions } from 'payload/types'
@@ -22,51 +23,50 @@ import { notFound } from 'next/navigation'
import queryString from 'qs'
import React, { Fragment } from 'react'
import type { InitPageResult } from '../../utilities/initPage'
import type { ServerSideEditViewProps } from '../Edit/types'
import type { AdminViewProps } from '../Root'
import type { GenerateEditViewMetadata } from './getMetaBySegment'
import { getMetaBySegment } from './getMetaBySegment'
import { getViewsFromConfig } from './getViewsFromConfig'
export const generateMetadata = async (args: {
config: Promise<SanitizedConfig>
params: {
collection?: string
global?: string
segments: string[]
}
}) => getMetaBySegment(args)
export const generateMetadata: GenerateEditViewMetadata = async (args) => getMetaBySegment(args)
export const Document: React.FC<AdminViewProps> = async ({
initPageResult,
params,
searchParams,
}) => {
const {
collectionConfig,
globalConfig,
locale,
permissions,
req,
req: {
i18n,
payload,
payload: {
config,
config: {
routes: { api: apiRoute },
serverURL,
},
},
user,
},
} = initPageResult
type Props = {
page: InitPageResult
params: { [key: string]: string | string[] }
searchParams: { [key: string]: string | string[] | undefined }
}
export const Document = async ({ page, params, searchParams }: Props) => {
const segments = Array.isArray(params?.segments) ? params.segments : []
const [entityType, entitySlug, createOrID] = segments
const collectionSlug = entityType === 'collections' ? entitySlug : undefined
const globalSlug = entitySlug === 'globals' ? entitySlug : undefined
const globalSlug = entityType === 'globals' ? entitySlug : undefined
const isCreating = createOrID === 'create'
const id = (collectionSlug && !isCreating && createOrID) || undefined
const isEditing = Boolean(globalSlug || (collectionSlug && !!id))
const {
i18n,
payload,
payload: { config },
user,
} = page.req
const {
routes: { api },
serverURL,
} = config
const { collectionConfig, globalConfig, locale, permissions, req } = page
let CustomView: SanitizedConfig['admin']['components']['views'][0]
let DefaultView: AdminViewComponent
let CustomView: EditViewComponent
let DefaultView: EditViewComponent
let data: DocumentType
let docPermissions: DocumentPermissions
let preferencesKey: string
@@ -78,13 +78,13 @@ export const Document = async ({ page, params, searchParams }: Props) => {
if (collectionConfig) {
docPermissions = permissions?.collections?.[collectionSlug]
fields = collectionConfig.fields
action = `${serverURL}${api}/${collectionSlug}${isEditing ? `/${id}` : ''}`
action = `${serverURL}${apiRoute}/${collectionSlug}${isEditing ? `/${id}` : ''}`
hasSavePermission =
(isEditing && permissions?.collections?.[collectionSlug]?.update?.permission) ||
(!isEditing && permissions?.collections?.[collectionSlug]?.create?.permission)
apiURL = `${serverURL}${api}/${collectionSlug}/${id}?locale=${locale.code}${
apiURL = `${serverURL}${apiRoute}/${collectionSlug}/${id}?locale=${locale.code}${
collectionConfig.versions?.drafts ? '&draft=true' : ''
}`
@@ -111,7 +111,7 @@ export const Document = async ({ page, params, searchParams }: Props) => {
locale: locale.code,
user,
})
} catch (error) {}
} catch (error) {} // eslint-disable-line no-empty
if (id) {
preferencesKey = `collection-${collectionSlug}-${id}`
@@ -122,9 +122,9 @@ export const Document = async ({ page, params, searchParams }: Props) => {
docPermissions = permissions?.globals?.[globalSlug]
fields = globalConfig.fields
hasSavePermission = isEditing && docPermissions?.update?.permission
action = `${serverURL}${api}/${globalSlug}`
action = `${serverURL}${apiRoute}/${globalSlug}`
apiURL = `${serverURL}${api}/${globalSlug}?locale=${locale.code}${
apiURL = `${serverURL}${apiRoute}/${globalSlug}?locale=${locale.code}${
globalConfig.versions?.drafts ? '&draft=true' : ''
}`
@@ -162,7 +162,7 @@ export const Document = async ({ page, params, searchParams }: Props) => {
equals: preferencesKey,
},
},
})) as any as { docs: { value: DocumentPreferences }[] }
})) as any as { docs: { value: DocumentPreferences }[] } // eslint-disable-line @typescript-eslint/no-explicit-any
const initialState = await buildStateFromSchema({
id,
@@ -185,37 +185,31 @@ export const Document = async ({ page, params, searchParams }: Props) => {
action: `${action}?${queryString.stringify(formQueryParams)}`,
apiURL,
canAccessAdmin: permissions?.canAccessAdmin,
collectionConfig,
collectionSlug,
config,
data,
docPermissions,
docPreferences,
globalConfig,
globalSlug,
hasSavePermission,
i18n,
initPageResult,
initialState,
isEditing,
locale,
payload,
permissions,
params,
searchParams,
updatedAt: data?.updatedAt?.toString(),
user,
}
return (
<Fragment>
<DocumentHeader
collectionConfig={collectionConfig}
config={req.payload.config}
config={payload.config}
globalConfig={globalConfig}
i18n={req.i18n}
i18n={i18n}
/>
<HydrateClientUser permissions={permissions} user={user} />
<SetDocumentInfo
action={action}
action={`${action}?${queryString.stringify(formQueryParams)}`}
apiURL={apiURL}
collectionSlug={collectionConfig?.slug}
disableActions={false}

View File

@@ -0,0 +1,4 @@
import { GenerateEditViewMetadata, getMetaBySegment } from './getMetaBySegment'
export const generateDocumentMetadata: GenerateEditViewMetadata = async (args) =>
getMetaBySegment(args)

View File

@@ -11,6 +11,7 @@ import {
getFormState,
useComponentMap,
useConfig,
useDocumentEvents,
useDocumentInfo,
} from '@payloadcms/ui'
import { Upload } from '@payloadcms/ui/elements'
@@ -66,13 +67,15 @@ export const DefaultEditView: React.FC = () => {
const globalConfig = globalSlug && globals.find((global) => global.slug === globalSlug)
const [schemaPath] = React.useState(collectionConfig?.slug || globalConfig?.slug)
const schemaPath = collectionConfig?.slug || globalConfig?.slug
const fieldMap = getFieldMap({
collectionSlug: collectionConfig?.slug,
globalSlug: globalConfig?.slug,
})
const { reportUpdate } = useDocumentEvents()
const operation = id ? 'update' : 'create'
const auth = collectionConfig ? collectionConfig.auth : undefined
@@ -87,11 +90,11 @@ export const DefaultEditView: React.FC = () => {
const onSave = useCallback(
async (json) => {
// reportUpdate({
// id,
// entitySlug: collectionConfig.slug,
// updatedAt: json?.result?.updatedAt || new Date().toISOString(),
// })
reportUpdate({
id,
entitySlug: collectionConfig.slug,
updatedAt: json?.result?.updatedAt || new Date().toISOString(),
})
// if (auth && id === user.id) {
// await refreshCookieAsync()
@@ -108,7 +111,7 @@ export const DefaultEditView: React.FC = () => {
id,
onSaveFromContext,
// refreshCookieAsync,
// reportUpdate
reportUpdate,
],
)
@@ -155,17 +158,6 @@ export const DefaultEditView: React.FC = () => {
}`}
type="withoutNav"
/>
{/* <Meta
description={`${isEditing ? t('general:editing') : t('general:creating')} - ${getTranslation(
collection.labels.singular,
i18n,
)}`}
keywords={`${getTranslation(collection.labels.singular, i18n)}, Payload, CMS`}
title={`${isEditing ? t('general:editing') : t('general:creating')} - ${getTranslation(
collection.labels.singular,
i18n,
)}`}
/> */}
{BeforeDocument}
{preventLeaveWithoutSaving && <LeaveWithoutSaving />}
<SetStepNav

View File

@@ -1,5 +1,6 @@
'use client'
import type { EditViewProps } from 'payload/config'
import type { EditViewProps } from 'payload/types'
import {
LoadingOverlay,
@@ -15,9 +16,11 @@ import { useCallback } from 'react'
export const EditViewClient: React.FC<EditViewProps> = () => {
const { id, collectionSlug, getDocPermissions, getVersions, globalSlug, setDocumentInfo } =
useDocumentInfo()
const {
routes: { admin: adminRoute },
} = useConfig()
const router = useRouter()
const { getComponentMap } = useComponentMap()
@@ -31,8 +34,8 @@ export const EditViewClient: React.FC<EditViewProps> = () => {
const onSave = useCallback(
async (json: { doc }) => {
getVersions()
getDocPermissions()
void getVersions()
void getDocPermissions()
if (!isEditing) {
router.push(`${adminRoute}/collections/${collectionSlug}/${json?.doc?.id}`)

View File

@@ -1,6 +1,6 @@
import React from 'react'
import type { ServerSideEditViewProps } from 'payload/types'
import type { ServerSideEditViewProps } from './types'
import React from 'react'
import { EditViewClient } from './index.client'
import { sanitizeEditViewProps } from './sanitizeEditViewProps'

View File

@@ -1,15 +1,10 @@
import type { EditViewProps } from 'payload/config'
import type { ServerSideEditViewProps } from './types'
import type { EditViewProps, ServerSideEditViewProps } from 'payload/types'
export const sanitizeEditViewProps = (props: ServerSideEditViewProps) => {
const clientSideProps = { ...props }
delete clientSideProps.payload
delete clientSideProps.config
delete clientSideProps.initPageResult.req
delete clientSideProps.initPageResult.collectionConfig
delete clientSideProps.initPageResult.globalConfig
delete clientSideProps.searchParams
delete clientSideProps.i18n
delete clientSideProps.collectionConfig
delete clientSideProps.globalConfig
return clientSideProps as EditViewProps
}

View File

@@ -1,43 +0,0 @@
import type { I18n } from '@payloadcms/translations'
import type { FormState } from '@payloadcms/ui'
import type { Permissions, User } from 'payload/auth'
import type { EditViewProps, Locale } from 'payload/config'
import type {
Data,
DocumentPermissions,
DocumentPreferences,
Payload,
SanitizedConfig,
} from 'payload/types'
export type ServerSideEditViewProps = EditViewProps & {
action?: string
apiURL: string
canAccessAdmin?: boolean
collectionConfig?: SanitizedConfig['collections'][0]
config: SanitizedConfig
data: Data
disableActions?: boolean
disableLeaveWithoutSaving?: boolean
docPermissions: DocumentPermissions
docPreferences: DocumentPreferences
globalConfig?: SanitizedConfig['globals'][0]
hasSavePermission?: boolean
i18n: I18n
id?: string
initialState?: FormState
isEditing?: boolean
locale: Locale
params?: {
collection?: string
global?: string
segments: string[]
}
payload: Payload
permissions: Permissions
searchParams: {
[key: string]: string | string[] | undefined
}
updatedAt: string
user: User
}
//# sourceMappingURL=types.d.ts.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAA;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC3D,OAAO,KAAK,EACV,IAAI,EACJ,mBAAmB,EACnB,mBAAmB,EACnB,OAAO,EACP,eAAe,EAChB,MAAM,eAAe,CAAA;AAEtB,MAAM,MAAM,uBAAuB,GAAG,aAAa,GAAG;IACpD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,gBAAgB,CAAC,EAAE,eAAe,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAA;IACpD,MAAM,EAAE,eAAe,CAAA;IACvB,IAAI,EAAE,IAAI,CAAA;IACV,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,yBAAyB,CAAC,EAAE,OAAO,CAAA;IACnC,cAAc,EAAE,mBAAmB,CAAA;IACnC,cAAc,EAAE,mBAAmB,CAAA;IACnC,YAAY,CAAC,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAA;IAC5C,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,IAAI,EAAE,IAAI,CAAA;IACV,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,YAAY,CAAC,EAAE,SAAS,CAAA;IACxB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE;QACP,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,QAAQ,EAAE,MAAM,EAAE,CAAA;KACnB,CAAA;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,WAAW,EAAE,WAAW,CAAA;IACxB,YAAY,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAA;KAAE,CAAA;IAE9D,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,IAAI,CAAA;CACX,CAAA"}

View File

@@ -1,37 +0,0 @@
import type { I18n } from '@payloadcms/translations'
import type { FormState } from '@payloadcms/ui'
import type { Permissions, User } from 'payload/auth'
import type { EditViewProps, Locale } from 'payload/config'
import type {
Data,
DocumentPermissions,
DocumentPreferences,
Payload,
SanitizedConfig,
} from 'payload/types'
export type ServerSideEditViewProps = EditViewProps & {
action?: string
apiURL: string
canAccessAdmin?: boolean
collectionConfig?: SanitizedConfig['collections'][0]
config: SanitizedConfig
data: Data
disableActions?: boolean
disableLeaveWithoutSaving?: boolean
docPermissions: DocumentPermissions
docPreferences: DocumentPreferences
globalConfig?: SanitizedConfig['globals'][0]
hasSavePermission?: boolean
i18n: I18n
id?: string
initialState?: FormState
isEditing?: boolean
locale: Locale
payload: Payload
permissions: Permissions
searchParams: { [key: string]: string | string[] | undefined }
// isLoading: boolean
updatedAt: string
user: User
}

View File

@@ -1,46 +1,21 @@
import type { Metadata } from 'next'
import type { SanitizedConfig } from 'payload/types'
import { Button, Email, Form, FormSubmit, MinimalTemplate, Translation } from '@payloadcms/ui'
import { Button, Email, Form, FormSubmit, Translation } from '@payloadcms/ui'
import Link from 'next/link'
import React, { Fragment } from 'react'
import type { InitPageResult } from '../../utilities/initPage'
import type { AdminViewProps } from '../Root'
import { getNextI18n } from '../../utilities/getNextI18n'
import { meta } from '../../utilities/meta'
export { generateForgotPasswordMetadata } from './meta'
export const generateMetadata = async ({
config: configPromise,
}: {
config: Promise<SanitizedConfig>
}): Promise<Metadata> => {
const config = await configPromise
const { t } = await getNextI18n({
config,
})
return meta({
config,
description: t('authentication:forgotPassword'),
keywords: t('authentication:forgotPassword'),
title: t('authentication:forgotPassword'),
})
}
type Props = {
baseClass: string
page: InitPageResult
}
export const ForgotPassword: React.FC<Props> = async ({ page }) => {
const { req } = page
export const forgotPasswordBaseClass = 'forgot-password'
export const ForgotPassword: React.FC<AdminViewProps> = ({ initPageResult }) => {
const {
i18n,
payload: { config },
user,
} = req
req: {
i18n,
payload: { config },
user,
},
} = initPageResult
const {
admin: { user: userSlug },
@@ -73,7 +48,7 @@ export const ForgotPassword: React.FC<Props> = async ({ page }) => {
/>
</p>
<br />
<Button buttonStyle="secondary" el="link" to={admin}>
<Button Link={Link} buttonStyle="secondary" el="link" to={admin}>
{i18n.t('general:backToDashboard')}
</Button>
</Fragment>

View File

@@ -0,0 +1,10 @@
import { meta } from '../../utilities/meta'
export const generateForgotPasswordMetadata = async ({ config, i18n: { t } }) => {
return meta({
config,
description: t('authentication:forgotPassword'),
keywords: t('authentication:forgotPassword'),
title: t('authentication:forgotPassword'),
})
}

View File

@@ -25,6 +25,7 @@ import {
SetViewActions,
UnpublishMany,
} from '@payloadcms/ui/elements'
import Link from 'next/link'
import { formatFilesize } from 'payload/utilities'
import React, { Fragment, useEffect } from 'react'
@@ -156,7 +157,7 @@ export const DefaultListView: React.FC = () => {
<div className={`${baseClass}__no-results`}>
<p>{i18n.t('general:noResults', { label: getTranslation(labels?.plural, i18n) })}</p>
{hasCreatePermission && newDocumentURL && (
<Button el="link" to={newDocumentURL}>
<Button Link={Link} el="link" to={newDocumentURL}>
{i18n.t('general:createNewLabel', {
label: getTranslation(labels?.singular, i18n),
})}

View File

@@ -1,7 +1,3 @@
import type { Metadata } from 'next'
import type { SanitizedConfig } from 'payload/types'
import { getTranslation } from '@payloadcms/translations'
import {
HydrateClientUser,
ListInfoProvider,
@@ -12,62 +8,24 @@ import { notFound } from 'next/navigation'
import { isEntityHidden } from 'payload/utilities'
import React, { Fragment } from 'react'
import type { InitPageResult } from '../../utilities/initPage'
import type { AdminViewProps } from '../Root'
import type { DefaultListViewProps, ListPreferences } from './Default/types'
import { getNextI18n } from '../../utilities/getNextI18n'
import { meta } from '../../utilities/meta'
import { DefaultListView } from './Default'
export const generateMetadata = async ({
config: configPromise,
params,
}: {
config: Promise<SanitizedConfig>
params: {
collection: string
}
}): Promise<Metadata> => {
let title: string = ''
const description: string = ''
const keywords: string = ''
export { generateListMetadata } from './meta'
const collectionSlug = params.collection
const config = await configPromise
const i18n = await getNextI18n({
config,
})
const collectionConfig = collectionSlug
? config?.collections?.find((collection) => collection.slug === collectionSlug)
: null
if (collectionConfig) {
title = getTranslation(collectionConfig.labels.plural, i18n)
}
return meta({
config,
description,
keywords,
title,
})
}
type Props = {
page: InitPageResult
params: { [key: string]: string | string[] }
searchParams: { [key: string]: string | string[] | undefined }
}
export const ListView = async ({ page, searchParams }: Props) => {
const { collectionConfig, permissions } = page
export const ListView: React.FC<AdminViewProps> = async ({ initPageResult, searchParams }) => {
const {
payload,
payload: { config },
user,
} = page.req
collectionConfig,
permissions,
req: {
payload,
payload: { config },
user,
},
} = initPageResult
const collectionSlug = collectionConfig?.slug
let listPreferences: ListPreferences
@@ -85,7 +43,7 @@ export const ListView = async ({ page, searchParams }: Props) => {
},
})
?.then((res) => res?.docs?.[0]?.value)) as ListPreferences
} catch (error) {}
} catch (error) {} // eslint-disable-line no-empty
const {
routes: { admin },

View File

@@ -0,0 +1,28 @@
import { meta } from '../../utilities/meta'
import { getTranslation } from '@payloadcms/translations'
import { GenerateViewMetadata } from '../Root'
import { Metadata } from 'next'
import { SanitizedCollectionConfig } from 'payload/types'
export const generateListMetadata = async (
args: Parameters<GenerateViewMetadata>[0] & {
collectionConfig: SanitizedCollectionConfig
},
): Promise<Metadata> => {
const { collectionConfig, config, i18n } = args
let title: string = ''
const description: string = ''
const keywords: string = ''
if (collectionConfig) {
title = getTranslation(collectionConfig.labels.plural, i18n)
}
return meta({
config,
description,
keywords,
title,
})
}

View File

@@ -1,5 +1,6 @@
'use client'
import type { EditViewProps, LivePreviewConfig } from 'payload/config'
import type { LivePreviewConfig } from 'payload/config'
import type { EditViewProps } from 'payload/types'
import { DndContext } from '@dnd-kit/core'
import React, { useCallback, useEffect, useState } from 'react'

View File

@@ -1,5 +1,6 @@
'use client'
import type { EditViewProps } from 'payload/config'
import type { EditViewProps } from 'payload/types'
import {
ShimmerEffect,

View File

@@ -1,5 +1,6 @@
'use client'
import type { EditViewProps } from 'payload/config'
import type { EditViewProps } from 'payload/types'
import { Chevron, LinkIcon, Popup, PopupList, X } from '@payloadcms/ui'
import React from 'react'

View File

@@ -1,5 +1,5 @@
'use client'
import type { EditViewProps } from 'payload/config'
import type { EditViewProps } from 'payload/types'
import { useDraggable } from '@dnd-kit/core'
import { DragHandle } from '@payloadcms/ui'

View File

@@ -1,7 +1,7 @@
'use client'
import type { FormProps } from '@payloadcms/ui'
import type { EditViewProps, LivePreviewConfig } from 'payload/config'
import type { Data } from 'payload/types'
import type { LivePreviewConfig } from 'payload/config'
import type { Data, EditViewProps } from 'payload/types'
import {
DocumentControls,

View File

@@ -1,21 +1,14 @@
import type { LivePreviewConfig } from 'payload/config'
import type { Data } from 'payload/types'
import type { ServerSideEditViewProps } from 'payload/types'
import React from 'react'
import type { InitPageResult } from '../../utilities/initPage'
import { LivePreviewClient } from './index.client'
import './index.scss'
type Props = {
data: Data
page: InitPageResult
params: { [key: string]: string | string[] }
searchParams: { [key: string]: string | string[] | undefined }
}
export const LivePreviewView: React.FC = async (props: Props) => {
const { page } = props
export const LivePreviewView: React.FC = async (props: ServerSideEditViewProps) => {
const { initPageResult } = props
const {
collectionConfig,
globalConfig,
@@ -27,7 +20,7 @@ export const LivePreviewView: React.FC = async (props: Props) => {
},
} = {},
} = {},
} = page
} = initPageResult
// TODO(JAKE): not sure what `data` is or what it should be
const { data = {} } = props
@@ -63,6 +56,7 @@ export const LivePreviewView: React.FC = async (props: Props) => {
? await livePreviewConfig.url({
data,
documentInfo: {}, // TODO: recreate this object server-side, see `useDocumentInfo`
// @ts-expect-error
locale,
})
: livePreviewConfig?.url

View File

@@ -1,5 +1,4 @@
'use client'
import type { FormState } from '@payloadcms/ui'
import {
Email,
@@ -15,6 +14,8 @@ import React from 'react'
const baseClass = 'login__form'
import type { FormState } from 'payload/types'
import { useRouter } from 'next/navigation'
import './index.scss'

View File

@@ -1,43 +1,18 @@
import type { Metadata } from 'next'
import type { SanitizedConfig } from 'payload/types'
import { Logo, MinimalTemplate } from '@payloadcms/ui'
import { Logo } from '@payloadcms/ui'
import { redirect } from 'next/navigation'
import React, { Fragment } from 'react'
import type { InitPageResult } from '../../utilities/initPage'
import type { AdminViewProps } from '../Root'
import { getNextI18n } from '../../utilities/getNextI18n'
import { meta } from '../../utilities/meta'
import { LoginForm } from './LoginForm'
import './index.scss'
export const generateMetadata = async ({
config: configPromise,
}: {
config: Promise<SanitizedConfig>
}): Promise<Metadata> => {
const config = await configPromise
export { generateLoginMetadata } from './meta'
const { t } = await getNextI18n({
config,
})
export const loginBaseClass = 'login'
return meta({
config,
description: `${t('authentication:login')}`,
keywords: `${t('authentication:login')}`,
title: t('authentication:login'),
})
}
type Props = {
baseClass: string
page: InitPageResult
searchParams: { [key: string]: string | string[] | undefined }
}
export const Login: React.FC<Props> = ({ baseClass, page, searchParams }) => {
const { req } = page
export const Login: React.FC<AdminViewProps> = ({ initPageResult, searchParams }) => {
const { req } = initPageResult
const {
payload: { config },
@@ -58,7 +33,7 @@ export const Login: React.FC<Props> = ({ baseClass, page, searchParams }) => {
return (
<Fragment>
<div className={`${baseClass}__brand`}>
<div className={`${loginBaseClass}__brand`}>
<Logo config={config} />
</div>
{Array.isArray(beforeLogin) && beforeLogin.map((Component, i) => <Component key={i} />)}

View File

@@ -0,0 +1,11 @@
import { meta } from '../../utilities/meta'
import { GenerateViewMetadata } from '../Root'
export const generateLoginMetadata: GenerateViewMetadata = async ({ config, i18n: { t } }) => {
return meta({
config,
description: `${t('authentication:login')}`,
keywords: `${t('authentication:login')}`,
title: t('authentication:login'),
})
}

View File

@@ -1,55 +1,29 @@
import type { Metadata } from 'next'
import type { SanitizedConfig } from 'payload/types'
import { MinimalTemplate } from '@payloadcms/ui'
import React from 'react'
import type { InitPageResult } from '../../utilities/initPage'
import type { AdminViewProps } from '../Root'
import { getNextI18n } from '../../utilities/getNextI18n'
import { meta } from '../../utilities/meta'
import { LogoutClient } from './LogoutClient'
import './index.scss'
const baseClass = 'logout'
export const generateMetadata = async ({
config: configPromise,
}: {
config: Promise<SanitizedConfig>
}): Promise<Metadata> => {
const config = await configPromise
export { generateLogoutMetadata } from './meta'
const { t } = await getNextI18n({
config,
})
return meta({
config,
description: `${t('authentication:logoutUser')}`,
keywords: `${t('authentication:logout')}`,
title: t('authentication:logout'),
})
}
type Props = {
baseClass: string
page: InitPageResult
searchParams: { [key: string]: string | string[] }
} & {
inactivity?: boolean
}
export const Logout: React.FC<Props> = ({ inactivity, page, searchParams }) => {
export const Logout: React.FC<
AdminViewProps & {
inactivity?: boolean
}
> = ({ inactivity, initPageResult, searchParams }) => {
const {
req: {
payload: { config },
payload: {
config: {
routes: { admin },
},
},
},
} = page
const {
routes: { admin },
} = config
} = initPageResult
return (
<MinimalTemplate className={baseClass}>
@@ -64,6 +38,6 @@ export const Logout: React.FC<Props> = ({ inactivity, page, searchParams }) => {
)
}
export const LogoutInactivity: React.FC<Props> = (props) => {
export const LogoutInactivity: React.FC<AdminViewProps> = (props) => {
return <Logout inactivity {...props} />
}

View File

@@ -0,0 +1,11 @@
import { meta } from '../../utilities/meta'
import { GenerateViewMetadata } from '../Root'
export const generateLogoutMetadata: GenerateViewMetadata = async ({ config, i18n: { t } }) => {
return meta({
config,
description: `${t('authentication:logoutUser')}`,
keywords: `${t('authentication:logout')}`,
title: t('authentication:logout'),
})
}

View File

@@ -1,5 +1,6 @@
'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'
@@ -41,7 +42,7 @@ const NotFound: React.FC<{
<Gutter className={`${baseClass}__wrap`}>
<h1>{t('general:nothingFound')}</h1>
<p>{t('general:sorryNotFound')}</p>
<Button className={`${baseClass}__button`} el="link" to={`${admin}`}>
<Button Link={Link} className={`${baseClass}__button`} el="link" to={`${admin}`}>
{t('general:backToDashboard')}
</Button>
</Gutter>

View File

@@ -1,6 +1,3 @@
import type { Metadata } from 'next'
import type { SanitizedConfig } from 'payload/types'
import {
Button,
ConfirmPassword,
@@ -14,40 +11,17 @@ import {
import Link from 'next/link'
import React from 'react'
import type { InitPageResult } from '../../utilities/initPage'
import type { AdminViewProps } from '../Root'
import { getNextI18n } from '../../utilities/getNextI18n'
import { meta } from '../../utilities/meta'
import './index.scss'
const baseClass = 'reset-password'
export const resetPasswordBaseClass = 'reset-password'
export const generateMetadata = async ({
config: configPromise,
}: {
config: Promise<SanitizedConfig>
}): Promise<Metadata> => {
const config = await configPromise
export { generateResetPasswordMetadata } from './meta'
const { t } = await getNextI18n({
config,
})
export const ResetPassword: React.FC<AdminViewProps> = ({ initPageResult, params }) => {
const { req } = initPageResult
return meta({
config,
description: t('authentication:resetPassword'),
keywords: t('authentication:resetPassword'),
title: t('authentication:resetPassword'),
})
}
type Props = {
page: InitPageResult
params: { [key: string]: string | string[] }
searchParams: { [key: string]: string | string[] }
}
export const ResetPassword: React.FC<Props> = ({ page, params }) => {
const { req } = page
const { token } = params
const {
@@ -74,8 +48,8 @@ export const ResetPassword: React.FC<Props> = ({ page, params }) => {
if (user) {
return (
<MinimalTemplate className={baseClass}>
<div className={`${baseClass}__wrap`}>
<MinimalTemplate className={resetPasswordBaseClass}>
<div className={`${resetPasswordBaseClass}__wrap`}>
<h1>{i18n.t('authentication:alreadyLoggedIn')}</h1>
<p>
<Translation
@@ -87,7 +61,7 @@ export const ResetPassword: React.FC<Props> = ({ page, params }) => {
/>
</p>
<br />
<Button buttonStyle="secondary" el="link" to={admin}>
<Button Link={Link} buttonStyle="secondary" el="link" to={admin}>
{i18n.t('general:backToDashboard')}
</Button>
</div>
@@ -96,8 +70,8 @@ export const ResetPassword: React.FC<Props> = ({ page, params }) => {
}
return (
<MinimalTemplate className={baseClass}>
<div className={`${baseClass}__wrap`}>
<MinimalTemplate className={resetPasswordBaseClass}>
<div className={`${resetPasswordBaseClass}__wrap`}>
<h1>{i18n.t('authentication:resetPassword')}</h1>
<Form
action={`${serverURL}${api}/${userSlug}/reset-password`}

View File

@@ -0,0 +1,14 @@
import { meta } from '../../utilities/meta'
import { GenerateViewMetadata } from '../Root'
export const generateResetPasswordMetadata: GenerateViewMetadata = async ({
config,
i18n: { t },
}) => {
return meta({
config,
description: t('authentication:resetPassword'),
keywords: t('authentication:resetPassword'),
title: t('authentication:resetPassword'),
})
}

View File

@@ -1,22 +1,25 @@
import type { SanitizedConfig } from 'payload/types'
import type { I18n } from '@payloadcms/translations'
import type { Metadata } from 'next'
import type { InitPageResult, SanitizedConfig } from 'payload/types'
import { DefaultTemplate, MinimalTemplate } from '@payloadcms/ui'
import { redirect } from 'next/navigation'
import React from 'react'
import { getNextI18n } from '../../utilities/getNextI18n'
import { initPage } from '../../utilities/initPage'
import { meta } from '../../utilities/meta'
import { Account } from '../Account'
import { CreateFirstUser } from '../CreateFirstUser'
import { Dashboard } from '../Dashboard'
import { Document as DocumentView } from '../Document'
import { ForgotPassword } from '../ForgotPassword'
import { ForgotPassword, forgotPasswordBaseClass } from '../ForgotPassword'
import { ListView } from '../List'
import { Login } from '../Login'
import { Login, loginBaseClass } from '../Login'
import { Logout, LogoutInactivity } from '../Logout'
import { ResetPassword } from '../ResetPassword'
import { ResetPassword, resetPasswordBaseClass } from '../ResetPassword'
import { Unauthorized } from '../Unauthorized'
import Verify from '../Verify'
import { Verify, verifyBaseClass } from '../Verify'
export { generatePageMetadata } from './meta'
type Args = {
config: Promise<SanitizedConfig>
@@ -28,11 +31,23 @@ type Args = {
}
}
export type GenerateViewMetadata = (args: {
config: SanitizedConfig
i18n: I18n
params?: { [key: string]: string | string[] }
}) => Promise<Metadata>
export type AdminViewProps = {
initPageResult: InitPageResult
params?: { [key: string]: string | string[] | undefined }
searchParams: { [key: string]: string | string[] | undefined }
}
const baseClasses = {
forgot: 'forgot-password',
login: 'login',
reset: 'reset-password',
verify: 'verify',
forgot: forgotPasswordBaseClass,
login: loginBaseClass,
reset: resetPasswordBaseClass,
verify: verifyBaseClass,
}
const oneSegmentViews = {
@@ -46,12 +61,19 @@ const oneSegmentViews = {
export const RootPage = async ({ config: configPromise, params, searchParams }: Args) => {
const config = await configPromise
let ViewToRender
const {
admin: { user: userSlug },
routes: { admin: adminRoute },
} = config
let ViewToRender: React.FC<AdminViewProps>
let templateClassName
let pageData
let initPageResult: InitPageResult
let templateType: 'default' | 'minimal' = 'default'
let route = config.routes.admin
let route = adminRoute
if (Array.isArray(params.segments)) {
route = route + '/' + params.segments.join('/')
}
@@ -70,7 +92,12 @@ export const RootPage = async ({ config: configPromise, params, searchParams }:
ViewToRender = Dashboard
templateClassName = 'dashboard'
templateType = 'default'
pageData = await initPage({ config, redirectUnauthenticatedUser: true, route, searchParams })
initPageResult = await initPage({
config,
redirectUnauthenticatedUser: true,
route,
searchParams,
})
break
}
case 1: {
@@ -81,13 +108,13 @@ export const RootPage = async ({ config: configPromise, params, searchParams }:
// --> /logout
// --> /logout-inactivity
// --> /unauthorized
pageData = await initPage({ config, route, searchParams })
initPageResult = await initPage({ config, route, searchParams })
ViewToRender = oneSegmentViews[segmentOne]
templateClassName = baseClasses[segmentOne]
templateType = 'minimal'
} else if (segmentOne === 'account') {
// --> /account
pageData = await initPage({
initPageResult = await initPage({
config,
redirectUnauthenticatedUser: true,
route,
@@ -102,14 +129,14 @@ export const RootPage = async ({ config: configPromise, params, searchParams }:
case 2: {
if (segmentOne === 'reset') {
// --> /reset/:token
pageData = await initPage({ config, route, searchParams })
initPageResult = await initPage({ config, route, searchParams })
ViewToRender = ResetPassword
templateClassName = baseClasses[segmentTwo]
templateType = 'minimal'
}
if (isCollection) {
// --> /collections/:collectionSlug
pageData = await initPage({
initPageResult = await initPage({
config,
redirectUnauthenticatedUser: true,
route,
@@ -120,7 +147,7 @@ export const RootPage = async ({ config: configPromise, params, searchParams }:
templateType = 'default'
} else if (isGlobal) {
// --> /globals/:globalSlug
pageData = await initPage({
initPageResult = await initPage({
config,
redirectUnauthenticatedUser: true,
route,
@@ -135,7 +162,7 @@ export const RootPage = async ({ config: configPromise, params, searchParams }:
default:
if (segmentTwo === 'verify') {
// --> /:collectionSlug/verify/:token
pageData = await initPage({ config, route, searchParams })
initPageResult = await initPage({ config, route, searchParams })
ViewToRender = Verify
templateClassName = 'verify'
templateType = 'minimal'
@@ -146,7 +173,7 @@ export const RootPage = async ({ config: configPromise, params, searchParams }:
// --> /collections/:collectionSlug/:id/versions
// --> /collections/:collectionSlug/:id/versions/:versionId
// --> /collections/:collectionSlug/:id/api
pageData = await initPage({
initPageResult = await initPage({
config,
redirectUnauthenticatedUser: true,
route,
@@ -161,7 +188,7 @@ export const RootPage = async ({ config: configPromise, params, searchParams }:
// --> /globals/:globalSlug/preview
// --> /globals/:globalSlug/versions/:versionId
// --> /globals/:globalSlug/api
pageData = await initPage({
initPageResult = await initPage({
config,
redirectUnauthenticatedUser: true,
route,
@@ -174,22 +201,47 @@ export const RootPage = async ({ config: configPromise, params, searchParams }:
break
}
if (pageData) {
const 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)
}
if (initPageResult) {
if (templateType === 'minimal') {
return (
<MinimalTemplate className={templateClassName}>
<ViewToRender page={pageData} params={params} searchParams={searchParams} />
<ViewToRender
initPageResult={initPageResult}
params={params}
searchParams={searchParams}
/>
</MinimalTemplate>
)
} else {
return (
<DefaultTemplate
config={config}
i18n={pageData.req.i18n}
permissions={pageData.permissions}
user={pageData.req.user}
i18n={initPageResult.req.i18n}
permissions={initPageResult.permissions}
user={initPageResult.req.user}
>
<ViewToRender page={pageData} params={params} searchParams={searchParams} />
<ViewToRender
initPageResult={initPageResult}
params={params}
searchParams={searchParams}
/>
</DefaultTemplate>
)
}
@@ -197,21 +249,3 @@ export const RootPage = async ({ config: configPromise, params, searchParams }:
return null
}
export const generateMeta = async ({ config: configPromise, params, searchParams }: Args) => {
const config = await configPromise
const { t } = await getNextI18n({
config,
})
return meta({
config,
description: 'Payload',
keywords: 'Payload',
title: 'Payload',
// description: `${t('authentication:logoutUser')}`,
// keywords: `${t('authentication:logout')}`,
// title: t('authentication:logout'),
})
}

View File

@@ -0,0 +1,135 @@
import { generateAccountMetadata } from '../Account'
import { generateCreateFirstUserMetadata } from '../CreateFirstUser'
import { generateDashboardMetadata } from '../Dashboard'
import { generateForgotPasswordMetadata } from '../ForgotPassword'
import { generateListMetadata } from '../List'
import { generateLoginMetadata } from '../Login'
import { generateResetPasswordMetadata } from '../ResetPassword'
import { generateUnauthorizedMetadata } from '../Unauthorized'
import { generateVerifyMetadata } from '../Verify'
import { Metadata } from 'next'
import { generateDocumentMetadata } from '../Document/meta'
import { getNextI18n } from '../../utilities/getNextI18n'
import { SanitizedConfig } from 'payload/types'
const oneSegmentMeta = {
'create-first-user': generateCreateFirstUserMetadata,
forgot: generateForgotPasswordMetadata,
login: generateLoginMetadata,
logout: generateUnauthorizedMetadata,
'logout-inactivity': generateUnauthorizedMetadata,
unauthorized: generateUnauthorizedMetadata,
}
type Args = {
config: Promise<SanitizedConfig>
params: {
[key: string]: string | string[]
}
searchParams: {
[key: string]: string | string[]
}
}
export const generatePageMetadata = async ({ config: configPromise, params }: Args) => {
const config = await configPromise
let route = config.routes.admin
if (Array.isArray(params.segments)) {
route = route + '/' + params.segments.join('/')
}
const segments = Array.isArray(params.segments) ? params.segments : []
const [segmentOne, segmentTwo] = segments
const isGlobal = segmentOne === 'globals'
const isCollection = segmentOne === 'collections'
const i18n = await getNextI18n({
config,
})
let meta: Metadata
// TODO: handle custom routes
const collectionConfig =
isCollection &&
segments.length > 1 &&
config?.collections?.find((collection) => collection.slug === segmentTwo)
const globalConfig =
isGlobal && segments.length > 1 && config?.globals?.find((global) => global.slug === segmentTwo)
switch (segments.length) {
case 0: {
meta = await generateDashboardMetadata({ config, i18n })
break
}
case 1: {
if (oneSegmentMeta[segmentOne] && segmentOne !== 'account') {
// --> /create-first-user
// --> /forgot
// --> /login
// --> /logout
// --> /logout-inactivity
// --> /unauthorized
meta = await oneSegmentMeta[segmentOne]({ config, i18n })
break
} else if (segmentOne === 'account') {
// --> /account
meta = await generateAccountMetadata({ config, i18n })
break
}
break
}
case 2: {
if (segmentOne === 'reset') {
// --> /reset/:token
meta = await generateResetPasswordMetadata({ config, i18n })
}
if (isCollection) {
// --> /collections/:collectionSlug
meta = await generateListMetadata({ config, i18n, collectionConfig })
} else if (isGlobal) {
// --> /globals/:globalSlug
meta = await generateDocumentMetadata({
config,
i18n,
globalConfig,
isEditing: false,
params,
})
}
break
}
default: {
if (segmentTwo === 'verify') {
// --> /:collectionSlug/verify/:token
meta = await generateVerifyMetadata({ config, i18n })
} else if (isCollection) {
// Custom Views
// --> /collections/:collectionSlug/:id
// --> /collections/:collectionSlug/:id/preview
// --> /collections/:collectionSlug/:id/versions
// --> /collections/:collectionSlug/:id/versions/:version
// --> /collections/:collectionSlug/:id/api
const isEditing = ['preview', 'versions', 'api', 'create'].includes(segmentTwo)
meta = await generateDocumentMetadata({ config, i18n, collectionConfig, isEditing, params })
} else if (isGlobal) {
// Custom Views
// --> /globals/:globalSlug/versions
// --> /globals/:globalSlug/versions/:version
// --> /globals/:globalSlug/preview
// --> /globals/:globalSlug/api
const isEditing = ['preview', 'versions', 'api', 'create'].includes(segmentTwo)
meta = await generateDocumentMetadata({ config, i18n, globalConfig, isEditing, params })
}
break
}
}
return meta
}

View File

@@ -1,5 +1,6 @@
'use client'
import { Button, useTranslation } from '@payloadcms/ui'
import Link from 'next/link'
import React from 'react'
export const UnauthorizedClient: React.FC<{ logoutRoute: string }> = ({ logoutRoute }) => {
@@ -10,7 +11,7 @@ export const UnauthorizedClient: React.FC<{ logoutRoute: string }> = ({ logoutRo
<h2>{t('error:unauthorized')}</h2>
<p>{t('error:notAllowedToAccessPage')}</p>
<br />
<Button el="link" to={logoutRoute}>
<Button Link={Link} el="link" to={logoutRoute}>
{t('authentication:logOut')}
</Button>
</React.Fragment>

View File

@@ -1,43 +1,22 @@
import type { Metadata } from 'next'
import { MinimalTemplate } from '@payloadcms/ui'
import React from 'react'
import type { InitPageResult } from '../../utilities/initPage'
import type { AdminViewProps } from '../Root'
import { meta } from '../../utilities/meta'
import { UnauthorizedClient } from './UnauthorizedClient'
export const generateMetadata = async ({ page }: { page: InitPageResult }): Promise<Metadata> => {
export { generateUnauthorizedMetadata } from './meta'
export const Unauthorized: React.FC<AdminViewProps> = ({ initPageResult }) => {
const {
req: {
payload: { config },
t,
payload: {
config: {
admin: { logoutRoute },
routes: { admin },
},
},
},
} = page
return meta({
config,
description: t('error:unauthorized'),
keywords: t('error:unauthorized'),
title: t('error:unauthorized'),
})
}
type Props = {
page: InitPageResult
}
export const Unauthorized: React.FC<Props> = ({ page }) => {
const {
req: {
payload: { config },
},
} = page
const {
admin: { logoutRoute },
routes: { admin },
} = config
} = initPageResult
return <UnauthorizedClient logoutRoute={`${admin}${logoutRoute}`} />
}

View File

@@ -0,0 +1,14 @@
import { meta } from '../../utilities/meta'
import { GenerateViewMetadata } from '../Root'
export const generateUnauthorizedMetadata: GenerateViewMetadata = async ({
config,
i18n: { t },
}) => {
return meta({
config,
description: t('error:unauthorized'),
keywords: t('error:unauthorized'),
title: t('error:unauthorized'),
})
}

View File

@@ -1,47 +1,20 @@
import type { Metadata } from 'next'
import type { SanitizedConfig } from 'payload/types'
import { Logo } from '@payloadcms/ui'
import { redirect } from 'next/navigation'
import React from 'react'
import type { InitPageResult } from '../../utilities/initPage'
import type { AdminViewProps } from '../Root'
import { getNextI18n } from '../../utilities/getNextI18n'
import { meta } from '../../utilities/meta'
import './index.scss'
const baseClass = 'verify'
export const verifyBaseClass = 'verify'
export const generateMetadata = async ({
config: configPromise,
}: {
config: Promise<SanitizedConfig>
}): Promise<Metadata> => {
const config = await configPromise
export { generateVerifyMetadata } from './meta'
const { t } = await getNextI18n({
config,
})
return meta({
config,
description: t('authentication:verifyUser'),
keywords: t('authentication:verify'),
title: t('authentication:verify'),
})
}
type Props = {
page: InitPageResult
params: { [key: string]: string | string[] }
searchParams: { [key: string]: string | string[] }
}
export const Verify: React.FC<Props> = async ({ page, params }) => {
export const Verify: React.FC<AdminViewProps> = async ({ initPageResult, params }) => {
// /:collectionSlug/verify/:token
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [collectionSlug, verify, token] = params.segments
const { req } = page
const { req } = initPageResult
const {
payload: { config },
@@ -68,11 +41,10 @@ export const Verify: React.FC<Props> = async ({ page, params }) => {
return (
<React.Fragment>
<div className={`${baseClass}__brand`}>
<div className={`${verifyBaseClass}__brand`}>
<Logo config={config} />
</div>
<h2>{textToRender}</h2>
</React.Fragment>
)
}
export default Verify

View File

@@ -0,0 +1,11 @@
import { meta } from '../../utilities/meta'
import { GenerateViewMetadata } from '../Root'
export const generateVerifyMetadata: GenerateViewMetadata = async ({ config, i18n: { t } }) => {
return meta({
config,
description: t('authentication:verifyUser'),
keywords: t('authentication:verify'),
title: t('authentication:verify'),
})
}

View File

@@ -1,27 +1,21 @@
import type { Option } from '@payloadcms/ui'
import type { CollectionPermission, GlobalPermission } from 'payload/auth'
import type { Document } from 'payload/types'
import type { Document, ServerSideEditViewProps } from 'payload/types'
import { notFound } from 'next/navigation'
import React from 'react'
import type { InitPageResult } from '../../utilities/initPage'
import { DefaultVersionView } from './Default'
type Props = {
page: InitPageResult
params: { [key: string]: string | string[] }
searchParams: { [key: string]: string | string[] | undefined }
}
export const VersionView: React.FC = async (props: Props) => {
const { page, params } = props
export const VersionView: React.FC = async (props: ServerSideEditViewProps) => {
const { initPageResult, params } = props
const {
collectionConfig,
globalConfig,
permissions,
req: { payload, payload: { config } = {}, user } = {},
} = page
} = initPageResult
// /entityType/:entitySlug/:id/versions/:versionID
const [entityType, entitySlug, id, versions, versionID] = params.segments

View File

@@ -1,10 +1,9 @@
import { getTranslation } from '@payloadcms/translations'
import type { ServerSideEditViewProps } from 'payload/types'
import { Gutter } from '@payloadcms/ui'
import { notFound } from 'next/navigation'
import React from 'react'
import type { ServerSideEditViewProps } from '../Edit/types'
import { SetStepNav } from '../Edit/Default/SetStepNav'
import { sanitizeEditViewProps } from '../Edit/sanitizeEditViewProps'
import { buildVersionColumns } from './buildColumns'
@@ -14,25 +13,28 @@ import './index.scss'
export const baseClass = 'versions'
export const VersionsView: React.FC<ServerSideEditViewProps> = async (props) => {
const { config, i18n, payload, searchParams, user } = props
const { id, initPageResult, searchParams } = props
const id = 'id' in props ? props.id : undefined
const collectionConfig = 'collectionConfig' in props && props?.collectionConfig
const globalConfig = 'globalConfig' in props && props?.globalConfig
const {
collectionConfig,
globalConfig,
req: {
i18n,
payload,
payload: { config },
user,
},
} = initPageResult
const collectionSlug = collectionConfig?.slug
const globalSlug = globalConfig?.slug
const { limit, page, sort } = searchParams
const {
routes: { admin: adminRoute, api: apiRoute },
routes: { api: apiRoute },
serverURL,
} = config
let docURL: string
let entityLabel: string
let slug: string
let editURL: string
let versionsData
if (collectionSlug) {
@@ -51,12 +53,8 @@ export const VersionsView: React.FC<ServerSideEditViewProps> = async (props) =>
},
})
} catch (error) {
console.error(error)
console.error(error) // eslint-disable-line no-console
}
docURL = `${serverURL}${apiRoute}/${slug}/${id}`
entityLabel = getTranslation(collectionConfig.labels.singular, i18n)
editURL = `${adminRoute}/collections/${collectionSlug}/${id}`
}
if (globalSlug) {
@@ -74,16 +72,12 @@ export const VersionsView: React.FC<ServerSideEditViewProps> = async (props) =>
},
})
} catch (error) {
console.error(error)
console.error(error) // eslint-disable-line no-console
}
if (!versionsData) {
return notFound()
}
docURL = `${serverURL}${apiRoute}/globals/${globalSlug}`
entityLabel = getTranslation(globalConfig.label, i18n)
editURL = `${adminRoute}/globals/${globalSlug}`
}
const columns = buildVersionColumns({

View File

@@ -14,11 +14,13 @@ const withPayload = (nextConfig = {}) => {
},
serverComponentsExternalPackages: [
...(nextConfig?.experimental?.serverComponentsExternalPackages || []),
'@payloadcms/db-mongodb',
'drizzle-kit',
'drizzle-kit/payload',
'libsql',
'pino',
'pino-pretty',
'payload',
],
},
webpack: (webpackConfig, webpackOptions) => {
@@ -67,4 +69,4 @@ const withPayload = (nextConfig = {}) => {
}
}
module.exports = { withPayload }
module.exports = withPayload

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "3.0.0-alpha.11",
"version": "3.0.0-alpha.18",
"description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT",
"main": "./dist/index.js",
@@ -113,10 +113,7 @@
"rimraf": "3.0.2",
"serve-static": "1.15.0",
"ts-essentials": "7.0.3",
"sharp": "0.33.2"
},
"peerDependencies": {
"sharp": "0.33.2"
"sharp": "0.32.6"
},
"engines": {
"node": ">=18.17.0"

View File

@@ -1,3 +1,29 @@
import type { ClientValidate, Field } from '../../fields/config/types'
export type Data = {
[key: string]: any
}
export type Row = {
blockType?: string
collapsed?: boolean
errorPaths?: Set<string>
id: string
}
export type FormField = {
disableFormData?: boolean
errorMessage?: string
errorPaths?: Set<string>
fieldSchema?: Field
initialValue: unknown
passesCondition?: boolean
rows?: Row[]
valid: boolean
validate?: ClientValidate
value: unknown
}
export type FormState = {
[path: string]: FormField
}

View File

@@ -23,6 +23,12 @@ export type {
DescriptionComponent,
DescriptionFunction,
} from './forms/FieldDescription'
export type { Data } from './forms/Form'
export type { Data, FormField, FormState, Row } from './forms/Form'
export type { LabelProps } from './forms/Label'
export type { RowLabel, RowLabelComponent } from './forms/RowLabel'
export type {
AdminViewComponent,
EditViewProps,
InitPageResult,
ServerSideEditViewProps,
} from './views/types'

View File

@@ -0,0 +1,61 @@
import type { Translations } from '@payloadcms/translations'
import type { DocumentPermissions, Permissions, User } from '../../auth'
import type { SanitizedCollectionConfig } from '../../collections/config/types'
import type { SanitizedGlobalConfig } from '../../globals/config/types'
import type { DocumentPreferences } from '../../preferences/types'
import type { PayloadRequest } from '../../types'
import type { Data, FormState } from '../types'
export type AdminViewConfig = {
Component: AdminViewComponent
/** Whether the path should be matched exactly or as a prefix */
exact?: boolean
path: string
sensitive?: boolean
strict?: boolean
}
export type AdminViewProps = {
canAccessAdmin?: boolean
user: User | null | undefined
}
export type AdminViewComponent = React.ComponentType<AdminViewProps>
export type AdminView = AdminViewComponent | AdminViewConfig
export type EditViewProps = {
collectionSlug?: string
globalSlug?: string
}
export type InitPageResult = {
collectionConfig?: SanitizedCollectionConfig
cookies: Map<string, string>
globalConfig?: SanitizedGlobalConfig
locale: Locale
permissions: Permissions
req: PayloadRequest
translations: Translations
}
export type ServerSideEditViewProps = EditViewProps & {
action?: string
apiURL: string
canAccessAdmin?: boolean
data: Data
disableActions?: boolean
disableLeaveWithoutSaving?: boolean
docPermissions: DocumentPermissions
docPreferences: DocumentPreferences
hasSavePermission?: boolean
id?: string
initPageResult: InitPageResult
initialState?: FormState
isEditing?: boolean
params?: { [key: string]: string | string[] }
searchParams: { [key: string]: string | string[] | undefined }
// isLoading: boolean
updatedAt: string
}

View File

@@ -11,7 +11,6 @@ import type {
import type { Auth, IncomingAuthType, User } from '../../auth/types'
import type {
Access,
AdminViewComponent,
EditViewComponent,
EditViewConfig,
Endpoint,
@@ -256,11 +255,11 @@ export type CollectionAdminOptions = {
* + `Version` - `/admin/collections/:collection/:id/versions/:version`
* + `CustomView` - `/admin/collections/:collection/:id/:path`
*/
API?: AdminViewComponent | Partial<EditViewConfig>
API?: EditViewComponent | Partial<EditViewConfig>
Default?: EditViewComponent | Partial<EditViewConfig>
LivePreview?: AdminViewComponent | Partial<EditViewConfig>
Version?: AdminViewComponent | Partial<EditViewConfig>
Versions?: AdminViewComponent | Partial<EditViewConfig>
LivePreview?: EditViewComponent | Partial<EditViewConfig>
Version?: EditViewComponent | Partial<EditViewConfig>
Versions?: EditViewComponent | Partial<EditViewConfig>
// TODO: uncomment these as they are built
// References?: EditView
// Relationships?: EditView

View File

@@ -62,6 +62,11 @@ const sanitizeCollections = (
if ('editor' in sanitized) delete sanitized.editor
if ('upload' in sanitized && typeof sanitized.upload === 'object') {
sanitized.upload = { ...sanitized.upload }
delete sanitized.upload.handlers
}
if ('admin' in sanitized) {
sanitized.admin = { ...sanitized.admin }
@@ -119,6 +124,8 @@ export const createClientConfig = async (
delete clientConfig.endpoints
delete clientConfig.db
delete clientConfig.editor
delete clientConfig.plugins
delete clientConfig.sharp
'localization' in clientConfig &&
clientConfig.localization &&

View File

@@ -1,20 +1,12 @@
/* eslint-disable import/no-dynamic-require */
/* eslint-disable global-require */
import type pino from 'pino'
// eslint-disable-next-line import/no-extraneous-dependencies
import path from 'path'
import type { SanitizedConfig } from './types'
import Logger from '../utilities/logger'
import { clientFiles } from './clientFiles'
import findConfig from './find'
import validate from './validate'
const loadConfig = async (logger?: pino.Logger): Promise<SanitizedConfig> => {
const localLogger = logger ?? Logger()
const loadConfig = async (): Promise<SanitizedConfig> => {
const configPath = findConfig()
clientFiles.forEach((ext) => {
@@ -28,18 +20,7 @@ const loadConfig = async (logger?: pino.Logger): Promise<SanitizedConfig> => {
if (config.default) config = await config.default
if (process.env.NODE_ENV !== 'production') {
config = await validate(config, localLogger)
}
return {
...config,
paths: {
config: configPath,
configDir: path.dirname(configPath),
rawConfig: configPath,
},
}
return config
}
export default loadConfig

View File

@@ -73,6 +73,12 @@ export default joi.object({
}),
user: joi.string(),
}),
bin: joi.array().items(
joi.object().keys({
key: joi.string(),
scriptPath: joi.string(),
}),
),
collections: joi.array(),
cookiePrefix: joi.string(),
cors: [joi.string().valid('*'), joi.array().items(joi.string())],
@@ -148,6 +154,7 @@ export default joi.object({
graphQL: joi.string(),
graphQLPlayground: joi.string(),
}),
secret: joi.string(),
serverURL: joi
.string()
.uri()
@@ -170,6 +177,7 @@ export default joi.object({
return value
}),
sharp: joi.any(),
telemetry: joi.boolean(),
typescript: joi.object({
declare: joi.boolean(),

View File

@@ -5,10 +5,12 @@ import type { Transporter } from 'nodemailer'
import type SMTPConnection from 'nodemailer/lib/smtp-connection'
import type { DestinationStream, LoggerOptions } from 'pino'
import type React from 'react'
import type { default as sharp } from 'sharp'
import type { DeepRequired } from 'ts-essentials'
import type { Payload } from '..'
import type { DocumentTab, RichTextAdapter } from '../admin/types'
import type { AdminView, ServerSideEditViewProps } from '../admin/views/types'
import type { User } from '../auth/types'
import type {
AfterErrorHook,
@@ -256,30 +258,7 @@ export type Endpoint<U = User> = {
root?: never
}
export type AdminViewConfig = {
Component: AdminViewComponent
/** Whether the path should be matched exactly or as a prefix */
exact?: boolean
path: string
sensitive?: boolean
strict?: boolean
}
export type AdminViewProps = {
canAccessAdmin?: boolean
user: User | null | undefined
}
export type AdminViewComponent = React.ComponentType<AdminViewProps>
export type AdminView = AdminViewComponent | AdminViewConfig
export type EditViewProps = {
collectionSlug?: string
globalSlug?: string
}
export type EditViewComponent = React.ComponentType<EditViewProps>
export type EditViewComponent = React.ComponentType<ServerSideEditViewProps>
export type EditViewConfig =
| {
@@ -293,7 +272,7 @@ export type EditViewConfig =
path?: string
}
| {
Component: AdminViewComponent // TODO: The `Edit` view Component is of type `React.FC<EditViewProps>`
Component: EditViewComponent
path: string
}
| {
@@ -307,7 +286,7 @@ export type EditViewConfig =
* All Tab properties become optional
* i.e. you can change just the label, if desired
*/
export type EditView = AdminViewComponent | EditViewConfig
export type EditView = EditViewComponent | EditViewConfig
export type Locale = {
/**
@@ -381,6 +360,23 @@ export type LocalizationConfig = Prettify<
LocalizationConfigWithLabels | LocalizationConfigWithNoLabels
>
export type SharpDependency = (
input?:
| ArrayBuffer
| Buffer
| Float32Array
| Float64Array
| Int8Array
| Int16Array
| Int32Array
| Uint8Array
| Uint8ClampedArray
| Uint16Array
| Uint32Array
| string,
options?: sharp.SharpOptions,
) => sharp.Sharp
/**
* This is the central configuration
*
@@ -657,6 +653,11 @@ export type Config = {
* @see https://payloadcms.com/docs/configuration/overview#options
*/
serverURL?: string
/**
* Pass in a local copy of Sharp if you'd like to use it.
*
*/
sharp?: SharpDependency
/** Send anonymous telemetry data about general usage. */
telemetry?: boolean
/** Control how typescript interfaces are generated from your collections. */

View File

@@ -35,7 +35,8 @@ export const readMigrationFiles = async ({
return Promise.all(
files.map(async (filePath) => {
const migration = eval(`(await import(${filePath}))`) as Migration
// eval used to circumvent errors bundling
const migration = eval(`require('${filePath.replaceAll('\\', '/')}')`)
migration.name = path.basename(filePath).split('.')?.[0]
return migration
}),

View File

@@ -130,9 +130,13 @@ export interface BaseDatabaseAdapter {
updateVersion: UpdateVersion
}
export type Init = (payload: Payload) => Promise<void>
export type Init = (payload: Payload) => Promise<void> | void
export type Connect = (payload: Payload) => Promise<void>
type ConnectArgs = {
hotReload: boolean
}
export type Connect = (args?: ConnectArgs) => Promise<void>
export type Destroy = (payload: Payload) => Promise<void>

View File

@@ -10,7 +10,6 @@ import type {
import type { User } from '../../auth/types'
import type {
Access,
AdminViewComponent,
EditViewComponent,
EditViewConfig,
Endpoint,
@@ -118,11 +117,11 @@ export type GlobalAdminOptions = {
* + `Version` - `/admin/globals/:id/versions/:version`
* + `CustomView` - `/admin/globals/:id/:path`
*/
API?: AdminViewComponent | Partial<EditViewConfig>
API?: EditViewComponent | Partial<EditViewConfig>
Default?: EditViewComponent | Partial<EditViewConfig>
LivePreview?: AdminViewComponent | Partial<EditViewConfig>
Version?: AdminViewComponent | Partial<EditViewConfig>
Versions?: AdminViewComponent | Partial<EditViewConfig>
LivePreview?: EditViewComponent | Partial<EditViewConfig>
Version?: EditViewComponent | Partial<EditViewConfig>
Versions?: EditViewComponent | Partial<EditViewConfig>
// TODO: uncomment these as they are built
// References?: EditView
// Relationships?: EditView

Some files were not shown because too many files have changed in this diff Show More