Compare commits
38 Commits
v3.0.0-bet
...
v3.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4ee623907 | ||
|
|
1cb1e5e8b3 | ||
|
|
e0699838e1 | ||
|
|
46f70d9df4 | ||
|
|
b7e2c59622 | ||
|
|
0cc7184023 | ||
|
|
e905675a05 | ||
|
|
4a20a63563 | ||
|
|
8d1fc6e8fb | ||
|
|
62744e79ac | ||
|
|
e8bed7b315 | ||
|
|
f2b8ddb299 | ||
|
|
ffd8ea516d | ||
|
|
3bf09703e9 | ||
|
|
c15d679b65 | ||
|
|
a422a0d568 | ||
|
|
edaeb1e29f | ||
|
|
6f35c356fe | ||
|
|
0b9397399a | ||
|
|
cdcc35ccdb | ||
|
|
442189ec48 | ||
|
|
5d1cc760c9 | ||
|
|
2f90683c7d | ||
|
|
3f5403a52a | ||
|
|
9bccdfd60a | ||
|
|
62666a9897 | ||
|
|
eb27b84854 | ||
|
|
c3480811d3 | ||
|
|
12ba820de4 | ||
|
|
95fcd13929 | ||
|
|
6141c5950b | ||
|
|
0040e1756c | ||
|
|
1ebd54b315 | ||
|
|
cdb2072a6d | ||
|
|
68553ff974 | ||
|
|
9a3bce1118 | ||
|
|
005befcbe2 | ||
|
|
e65b6478c9 |
14
.vscode/launch.json
vendored
14
.vscode/launch.json
vendored
@@ -56,6 +56,20 @@
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "node --no-deprecation test/dev.js fields-relationship",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Fields-Relationship",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "node --no-deprecation test/dev.js login-with-username",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Login-With-Username",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "pnpm run dev plugin-cloud-storage",
|
||||
"cwd": "${workspaceFolder}",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Building Your Own Plugin
|
||||
label: Build Your Own
|
||||
order: 50
|
||||
order: 20
|
||||
desc: Starting to build your own plugin? Find everything you need and learn best practices with the Payload plugin template.
|
||||
keywords: plugins, template, config, configuration, extensions, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
@@ -159,36 +159,60 @@ export const seed = async (payload: Payload): Promise<void> => {
|
||||
|
||||
```
|
||||
|
||||
## Overview of the src folder
|
||||
## Building a Plugin
|
||||
|
||||
Now that we have our environment setup and dev project ready to go - it's time to build the plugin!
|
||||
|
||||
**index.ts**
|
||||
|
||||
First up, the `src/index.ts` file - this is where the plugin should be imported from. It is best practice not to build the plugin directly in this file, instead we use this to export the plugin and types from their respective files.
|
||||
|
||||
**Plugin.ts**
|
||||
|
||||
To reiterate, the essence of a [Payload Plugin](./overview) is simply to extend the [Payload Config](../configuration/overview) - and that is exactly what we are doing in this file.
|
||||
|
||||
```
|
||||
import type { Config } from 'payload'
|
||||
|
||||
export const samplePlugin =
|
||||
(pluginOptions: PluginTypes) =>
|
||||
(incomingConfig: Config): Config => {
|
||||
// create copy of incoming config
|
||||
let config = { ...incomingConfig }
|
||||
|
||||
// do something cool with the config here
|
||||
/**
|
||||
* This is where you could modify the
|
||||
* config based on the plugin options
|
||||
*/
|
||||
|
||||
// If you wanted to add a new collection:
|
||||
config.collections = [
|
||||
...(config.collections || []),
|
||||
newCollection,
|
||||
]
|
||||
|
||||
// If you wanted to add a new global:
|
||||
config.globals = [
|
||||
...(config.globals || []),
|
||||
newGlobal,
|
||||
]
|
||||
|
||||
/**
|
||||
* If you wanted to add a new field to a collection:
|
||||
*
|
||||
* 1. Loop over collections
|
||||
* 2. Find the collection you want to add the field to
|
||||
* 3. Add the field to the collection
|
||||
*/
|
||||
|
||||
// If you wanted to add to the onInit:
|
||||
config.onInit = async payload => {
|
||||
if (incomingConfig.onInit) await incomingConfig.onInit(payload)
|
||||
// Add additional onInit code here
|
||||
}
|
||||
|
||||
// Finally, return the modified config
|
||||
return config
|
||||
}
|
||||
```
|
||||
|
||||
1. First, you need to receive the existing Payload Config along with any plugin options.
|
||||
2. Then set the variable `config` to be equal to a copy of the existing config.
|
||||
3. From here, you can extend the config however you like!
|
||||
4. Finally, return the config and you're all set.
|
||||
To reiterate, the essence of a [Payload Plugin](./overview) is simply to extend the [Payload Config](../configuration/overview) - and that is exactly what we are doing in this file.
|
||||
|
||||
## Spread Syntax
|
||||
|
||||
### Spread syntax
|
||||
|
||||
[Spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) (or the spread operator) is a feature in JavaScript that uses the dot notation **(...)** to spread elements from arrays, strings, or objects into various contexts.
|
||||
|
||||
@@ -206,7 +230,7 @@ config.collections = [
|
||||
|
||||
First, you need to spread the `config.collections` to ensure that we don't lose the existing collections. Then you can add any additional collections, just as you would in a regular Payload Config.
|
||||
|
||||
This same logic is applied to other properties like admin, globals, hooks:
|
||||
This same logic is applied to other array and object like properties such as admin, globals and hooks:
|
||||
|
||||
```
|
||||
config.globals = [
|
||||
@@ -220,7 +244,10 @@ config.hooks = {
|
||||
}
|
||||
```
|
||||
|
||||
Some properties will be slightly different to extend, for instance the `onInit` property:
|
||||
### Extending functions
|
||||
Function properties cannot use spread syntax. The way to extend them is to execute the existing function if it exists and then run your additional functionality.
|
||||
|
||||
Here is an example extending the `onInit` property:
|
||||
|
||||
```
|
||||
config.onInit = async payload => {
|
||||
@@ -231,10 +258,6 @@ config.onInit = async payload => {
|
||||
}
|
||||
```
|
||||
|
||||
If you wish to add to the `onInit`, you must include the async/await. We don't use spread syntax in this case, instead you must await the existing `onInit` before running additional functionality.
|
||||
|
||||
In the template, we have stubbed out a basic `onInitExtension` file that you can use, if not needed feel free to delete it.
|
||||
|
||||
## Types
|
||||
|
||||
If your plugin has options, you should define and provide types for these options in a separate file which gets exported from the main `index.ts`.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Form Builder Plugin
|
||||
label: Form Builder
|
||||
order: 20
|
||||
order: 40
|
||||
desc: Easily build and manage forms from the Admin Panel. Send dynamic, personalized emails and even accept and process payments.
|
||||
keywords: plugins, plugin, form, forms, form builder
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Nested Docs Plugin
|
||||
label: Nested Docs
|
||||
order: 20
|
||||
order: 40
|
||||
desc: Nested documents in a parent, child, and sibling relationship.
|
||||
keywords: plugins, nested, documents, parent, child, sibling, relationship
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Redirects Plugin
|
||||
label: Redirects
|
||||
order: 20
|
||||
order: 40
|
||||
desc: Automatically create redirects for your Payload application
|
||||
keywords: plugins, redirects, redirect, plugin, payload, cms, seo, indexing, search, search engine
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Search Plugin
|
||||
label: Search
|
||||
order: 20
|
||||
order: 40
|
||||
desc: Generates records of your documents that are extremely fast to search on.
|
||||
keywords: plugins, search, search plugin, search engine, search index, search results, search bar, search box, search field, search form, search input
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Sentry Plugin
|
||||
label: Sentry
|
||||
order: 20
|
||||
order: 40
|
||||
desc: Integrate Sentry error tracking into your Payload application
|
||||
keywords: plugins, sentry, error, tracking, monitoring, logging, bug, reporting, performance
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: SEO Plugin
|
||||
label: SEO
|
||||
order: 20
|
||||
order: 30
|
||||
desc: Manage SEO metadata from your Payload admin
|
||||
keywords: plugins, seo, meta, search, engine, ranking, google
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Stripe Plugin
|
||||
label: Stripe
|
||||
order: 20
|
||||
order: 40
|
||||
desc: Easily accept payments with Stripe
|
||||
keywords: plugins, stripe, payments, ecommerce
|
||||
---
|
||||
|
||||
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -96,7 +96,7 @@
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.7.0",
|
||||
"@libsql/client": "0.6.2",
|
||||
"@next/bundle-analyzer": "15.0.0-canary.53",
|
||||
"@next/bundle-analyzer": "15.0.0-canary.104",
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@payloadcms/eslint-plugin": "workspace:*",
|
||||
"@payloadcms/live-preview-react": "workspace:*",
|
||||
@@ -131,15 +131,15 @@
|
||||
"lint-staged": "15.2.7",
|
||||
"minimist": "1.2.8",
|
||||
"mongodb-memory-server": "^9.0",
|
||||
"next": "15.0.0-canary.53",
|
||||
"next": "15.0.0-canary.104",
|
||||
"open": "^10.1.0",
|
||||
"p-limit": "^5.0.0",
|
||||
"playwright": "1.43.0",
|
||||
"playwright-core": "1.43.0",
|
||||
"prettier": "3.3.2",
|
||||
"prompts": "2.4.2",
|
||||
"react": "^19.0.0-rc-6230622a1a-20240610",
|
||||
"react-dom": "^19.0.0-rc-6230622a1a-20240610",
|
||||
"react": "^19.0.0-rc-06d0b89e-20240801",
|
||||
"react-dom": "^19.0.0-rc-06d0b89e-20240801",
|
||||
"rimraf": "3.0.2",
|
||||
"semver": "^7.5.4",
|
||||
"sharp": "0.32.6",
|
||||
@@ -153,8 +153,8 @@
|
||||
"typescript": "5.5.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610"
|
||||
"react": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.20.2 || >=20.9.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -74,9 +74,11 @@ export async function updatePayloadInProject(
|
||||
info('Payload packages updated successfully.')
|
||||
|
||||
info(`Updating Payload Next.js files...`)
|
||||
const templateFilesPath = dirname.endsWith('dist')
|
||||
? path.resolve(dirname, '../..', 'dist/template')
|
||||
: path.resolve(dirname, '../../../../templates/blank-3.0')
|
||||
|
||||
const templateFilesPath =
|
||||
process.env.JEST_WORKER_ID !== undefined
|
||||
? path.resolve(dirname, '../../../../templates/blank-3.0')
|
||||
: path.resolve(dirname, '../..', 'dist/template')
|
||||
|
||||
const templateSrcDir = path.resolve(templateFilesPath, 'src/app/(payload)')
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -54,6 +54,10 @@ export const connect: Connect = async function connect(
|
||||
this.payload.logger.info('---- DROPPED DATABASE ----')
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'production' && this.prodMigrations) {
|
||||
await this.migrate({ migrations: this.prodMigrations })
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
this.payload.logger.error(`Error: cannot connect to MongoDB. Details: ${err.message}`, err)
|
||||
|
||||
@@ -8,7 +8,7 @@ import mongoose from 'mongoose'
|
||||
import path from 'path'
|
||||
import { createDatabaseAdapter } from 'payload'
|
||||
|
||||
import type { CollectionModel, GlobalModel } from './types.js'
|
||||
import type { CollectionModel, GlobalModel, MigrateDownArgs, MigrateUpArgs } from './types.js'
|
||||
|
||||
import { connect } from './connect.js'
|
||||
import { count } from './count.js'
|
||||
@@ -78,6 +78,11 @@ export interface Args {
|
||||
* typed as any to avoid dependency
|
||||
*/
|
||||
mongoMemoryServer?: MongoMemoryReplSet
|
||||
prodMigrations?: {
|
||||
down: (args: MigrateDownArgs) => Promise<void>
|
||||
name: string
|
||||
up: (args: MigrateUpArgs) => Promise<void>
|
||||
}[]
|
||||
transactionOptions?: TransactionOptions | false
|
||||
/** The URL to connect to MongoDB or false to start payload and prevent connecting */
|
||||
url: false | string
|
||||
@@ -90,6 +95,11 @@ export type MongooseAdapter = {
|
||||
connection: Connection
|
||||
globals: GlobalModel
|
||||
mongoMemoryServer: MongoMemoryReplSet
|
||||
prodMigrations?: {
|
||||
down: (args: MigrateDownArgs) => Promise<void>
|
||||
name: string
|
||||
up: (args: MigrateUpArgs) => Promise<void>
|
||||
}[]
|
||||
sessions: Record<number | string, ClientSession>
|
||||
versions: {
|
||||
[slug: string]: CollectionModel
|
||||
@@ -107,6 +117,11 @@ declare module 'payload' {
|
||||
connection: Connection
|
||||
globals: GlobalModel
|
||||
mongoMemoryServer: MongoMemoryReplSet
|
||||
prodMigrations?: {
|
||||
down: (args: MigrateDownArgs) => Promise<void>
|
||||
name: string
|
||||
up: (args: MigrateUpArgs) => Promise<void>
|
||||
}[]
|
||||
sessions: Record<number | string, ClientSession>
|
||||
transactionOptions: TransactionOptions
|
||||
versions: {
|
||||
@@ -121,6 +136,7 @@ export function mongooseAdapter({
|
||||
disableIndexHints = false,
|
||||
migrationDir: migrationDirArg,
|
||||
mongoMemoryServer,
|
||||
prodMigrations,
|
||||
transactionOptions = {},
|
||||
url,
|
||||
}: Args): DatabaseAdapterObj {
|
||||
@@ -167,6 +183,7 @@ export function mongooseAdapter({
|
||||
migrateFresh,
|
||||
migrationDir,
|
||||
payload,
|
||||
prodMigrations,
|
||||
queryDrafts,
|
||||
rollbackTransaction,
|
||||
updateGlobal,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -47,7 +47,7 @@
|
||||
"dependencies": {
|
||||
"@payloadcms/drizzle": "workspace:*",
|
||||
"console-table-printer": "2.11.2",
|
||||
"drizzle-kit": "0.23.1-7816536",
|
||||
"drizzle-kit": "0.23.2-df9e596",
|
||||
"drizzle-orm": "0.32.1",
|
||||
"pg": "8.11.3",
|
||||
"prompts": "2.4.2",
|
||||
|
||||
@@ -91,4 +91,8 @@ export const connect: Connect = async function connect(
|
||||
}
|
||||
|
||||
if (typeof this.resolveInitializing === 'function') this.resolveInitializing()
|
||||
|
||||
if (process.env.NODE_ENV === 'production' && this.prodMigrations) {
|
||||
await this.migrate({ migrations: this.prodMigrations })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
|
||||
import type { DrizzleSnapshotJSON } from 'drizzle-kit/api'
|
||||
import type { CreateMigration } from 'payload'
|
||||
|
||||
import fs from 'fs'
|
||||
import { createRequire } from 'module'
|
||||
import path from 'path'
|
||||
import { getPredefinedMigration } from 'payload'
|
||||
import { getPredefinedMigration, writeMigrationIndex } from 'payload'
|
||||
import prompts from 'prompts'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
@@ -25,7 +25,7 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir)
|
||||
}
|
||||
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/payload')
|
||||
const { generateDrizzleJson, generateMigration, upPgSnapshot } = require('drizzle-kit/api')
|
||||
const drizzleJsonAfter = generateDrizzleJson(this.schema)
|
||||
const [yyymmdd, hhmmss] = new Date().toISOString().split('T')
|
||||
const formattedDate = yyymmdd.replace(/\D/g, '')
|
||||
@@ -64,9 +64,11 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
.reverse()?.[0]
|
||||
|
||||
if (latestSnapshot) {
|
||||
drizzleJsonBefore = JSON.parse(
|
||||
fs.readFileSync(`${dir}/${latestSnapshot}`, 'utf8'),
|
||||
) as DrizzleSnapshotJSON
|
||||
drizzleJsonBefore = JSON.parse(fs.readFileSync(`${dir}/${latestSnapshot}`, 'utf8'))
|
||||
|
||||
if (drizzleJsonBefore.version < drizzleJsonAfter.version) {
|
||||
drizzleJsonBefore = upPgSnapshot(drizzleJsonBefore)
|
||||
}
|
||||
}
|
||||
|
||||
const sqlStatementsUp = await generateMigration(drizzleJsonBefore, drizzleJsonAfter)
|
||||
@@ -113,5 +115,8 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
upSQL: upSQL || ` // Migration code`,
|
||||
}),
|
||||
)
|
||||
|
||||
writeMigrationIndex({ migrationsDir: payload.db.migrationDir })
|
||||
|
||||
payload.logger.info({ msg: `Migration created at ${filePath}.ts` })
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
|
||||
import type { DrizzleSnapshotJSON } from 'drizzle-kit/api'
|
||||
|
||||
export const defaultDrizzleSnapshot: DrizzleSnapshotJSON = {
|
||||
id: '00000000-0000-0000-0000-000000000000',
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
updateOne,
|
||||
updateVersion,
|
||||
} from '@payloadcms/drizzle'
|
||||
import { type PgSchema, pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
|
||||
import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
|
||||
import { createDatabaseAdapter } from 'payload'
|
||||
|
||||
import type { Args, PostgresAdapter } from './types.js'
|
||||
@@ -94,6 +94,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
|
||||
pgSchema: adapterSchema,
|
||||
pool: undefined,
|
||||
poolOptions: args.pool,
|
||||
prodMigrations: args.prodMigrations,
|
||||
push: args.push,
|
||||
relations: {},
|
||||
relationshipsSuffix: args.relationshipsSuffix || '_rels',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { TransactionPg } from '@payloadcms/drizzle/types'
|
||||
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
|
||||
import type { DrizzleSnapshotJSON } from 'drizzle-kit/api'
|
||||
import type { Payload, PayloadRequest } from 'payload'
|
||||
|
||||
import { sql } from 'drizzle-orm'
|
||||
@@ -43,7 +43,7 @@ export const migratePostgresV2toV3 = async ({ debug, payload, req }: Args) => {
|
||||
const dir = payload.db.migrationDir
|
||||
|
||||
// get the drizzle migrateUpSQL from drizzle using the last schema
|
||||
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/payload')
|
||||
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/api')
|
||||
const drizzleJsonAfter = generateDrizzleJson(adapter.schema)
|
||||
|
||||
// Get the previous migration snapshot
|
||||
|
||||
@@ -2,4 +2,4 @@ import type { RequireDrizzleKit } from '@payloadcms/drizzle/types'
|
||||
|
||||
import { createRequire } from 'module'
|
||||
const require = createRequire(import.meta.url)
|
||||
export const requireDrizzleKit: RequireDrizzleKit = () => require('drizzle-kit/payload')
|
||||
export const requireDrizzleKit: RequireDrizzleKit = () => require('drizzle-kit/api')
|
||||
|
||||
@@ -4,7 +4,7 @@ import type {
|
||||
DrizzleAdapter,
|
||||
TransactionPg,
|
||||
} from '@payloadcms/drizzle/types'
|
||||
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
|
||||
import type { DrizzleSnapshotJSON } from 'drizzle-kit/api'
|
||||
import type {
|
||||
ColumnBaseConfig,
|
||||
ColumnDataType,
|
||||
@@ -33,6 +33,11 @@ export type Args = {
|
||||
logger?: DrizzleConfig['logger']
|
||||
migrationDir?: string
|
||||
pool: PoolConfig
|
||||
prodMigrations?: {
|
||||
down: (args: MigrateDownArgs) => Promise<void>
|
||||
name: string
|
||||
up: (args: MigrateUpArgs) => Promise<void>
|
||||
}[]
|
||||
push?: boolean
|
||||
relationshipsSuffix?: string
|
||||
/**
|
||||
@@ -136,6 +141,11 @@ export type PostgresAdapter = {
|
||||
pgSchema?: Schema
|
||||
pool: Pool
|
||||
poolOptions: Args['pool']
|
||||
prodMigrations?: {
|
||||
down: (args: MigrateDownArgs) => Promise<void>
|
||||
name: string
|
||||
up: (args: MigrateUpArgs) => Promise<void>
|
||||
}[]
|
||||
push: boolean
|
||||
rejectInitializing: () => void
|
||||
relations: Record<string, GenericRelation>
|
||||
@@ -178,6 +188,11 @@ declare module 'payload' {
|
||||
pgSchema?: { table: PgTableFn } | PgSchema
|
||||
pool: Pool
|
||||
poolOptions: Args['pool']
|
||||
prodMigrations?: {
|
||||
down: (args: MigrateDownArgs) => Promise<void>
|
||||
name: string
|
||||
up: (args: MigrateUpArgs) => Promise<void>
|
||||
}[]
|
||||
push: boolean
|
||||
rejectInitializing: () => void
|
||||
relationshipsSuffix?: string
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-sqlite",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "The officially supported SQLite database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -46,7 +46,7 @@
|
||||
"@libsql/client": "^0.6.2",
|
||||
"@payloadcms/drizzle": "workspace:*",
|
||||
"console-table-printer": "2.11.2",
|
||||
"drizzle-kit": "0.23.1-7816536",
|
||||
"drizzle-kit": "0.23.2-df9e596",
|
||||
"drizzle-orm": "0.32.1",
|
||||
"prompts": "2.4.2",
|
||||
"to-snake-case": "1.0.0",
|
||||
|
||||
@@ -52,4 +52,8 @@ export const connect: Connect = async function connect(
|
||||
}
|
||||
|
||||
if (typeof this.resolveInitializing === 'function') this.resolveInitializing()
|
||||
|
||||
if (process.env.NODE_ENV === 'production' && this.prodMigrations) {
|
||||
await this.migrate({ migrations: this.prodMigrations })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
|
||||
import type { DrizzleSnapshotJSON } from 'drizzle-kit/api'
|
||||
import type { CreateMigration } from 'payload'
|
||||
|
||||
import fs from 'fs'
|
||||
import { createRequire } from 'module'
|
||||
import path from 'path'
|
||||
import { getPredefinedMigration } from 'payload'
|
||||
import { getPredefinedMigration, writeMigrationIndex } from 'payload'
|
||||
import prompts from 'prompts'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
@@ -25,7 +25,7 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir)
|
||||
}
|
||||
const { generateSQLiteDrizzleJson, generateSQLiteMigration } = require('drizzle-kit/payload')
|
||||
const { generateSQLiteDrizzleJson, generateSQLiteMigration } = require('drizzle-kit/api')
|
||||
const drizzleJsonAfter = await generateSQLiteDrizzleJson(this.schema)
|
||||
const [yyymmdd, hhmmss] = new Date().toISOString().split('T')
|
||||
const formattedDate = yyymmdd.replace(/\D/g, '')
|
||||
@@ -112,5 +112,8 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
upSQL: upSQL || ` // Migration code`,
|
||||
}),
|
||||
)
|
||||
|
||||
writeMigrationIndex({ migrationsDir: payload.db.migrationDir })
|
||||
|
||||
payload.logger.info({ msg: `Migration created at ${filePath}.ts` })
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { DrizzleSQLiteSnapshotJSON } from 'drizzle-kit/payload'
|
||||
import type { DrizzleSQLiteSnapshotJSON } from 'drizzle-kit/api'
|
||||
|
||||
export const defaultDrizzleSnapshot: DrizzleSQLiteSnapshotJSON = {
|
||||
id: '00000000-0000-0000-0000-000000000000',
|
||||
|
||||
@@ -93,6 +93,7 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
|
||||
localesSuffix: args.localesSuffix || '_locales',
|
||||
logger: args.logger,
|
||||
operators,
|
||||
prodMigrations: args.prodMigrations,
|
||||
push: args.push,
|
||||
relations: {},
|
||||
relationshipsSuffix: args.relationshipsSuffix || '_rels',
|
||||
|
||||
@@ -10,6 +10,6 @@ export const requireDrizzleKit: RequireDrizzleKit = () => {
|
||||
const {
|
||||
generateSQLiteDrizzleJson: generateDrizzleJson,
|
||||
pushSQLiteSchema: pushSchema,
|
||||
} = require('drizzle-kit/payload')
|
||||
} = require('drizzle-kit/api')
|
||||
return { generateDrizzleJson, pushSchema }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AnySQLiteColumn} from 'drizzle-orm/sqlite-core';
|
||||
import type { AnySQLiteColumn } from 'drizzle-orm/sqlite-core'
|
||||
|
||||
import { index, uniqueIndex } from 'drizzle-orm/sqlite-core'
|
||||
|
||||
|
||||
@@ -18,6 +18,11 @@ export type Args = {
|
||||
localesSuffix?: string
|
||||
logger?: DrizzleConfig['logger']
|
||||
migrationDir?: string
|
||||
prodMigrations?: {
|
||||
down: (args: MigrateDownArgs) => Promise<void>
|
||||
name: string
|
||||
up: (args: MigrateUpArgs) => Promise<void>
|
||||
}[]
|
||||
push?: boolean
|
||||
relationshipsSuffix?: string
|
||||
schemaName?: string
|
||||
@@ -100,6 +105,11 @@ export type SQLiteAdapter = {
|
||||
localesSuffix?: string
|
||||
logger: DrizzleConfig['logger']
|
||||
operators: Operators
|
||||
prodMigrations?: {
|
||||
down: (args: MigrateDownArgs) => Promise<void>
|
||||
name: string
|
||||
up: (args: MigrateUpArgs) => Promise<void>
|
||||
}[]
|
||||
push: boolean
|
||||
rejectInitializing: () => void
|
||||
relations: Record<string, GenericRelation>
|
||||
@@ -139,6 +149,11 @@ declare module 'payload' {
|
||||
initializing: Promise<void>
|
||||
localesSuffix?: string
|
||||
logger: DrizzleConfig['logger']
|
||||
prodMigrations?: {
|
||||
down: (args: MigrateDownArgs) => Promise<void>
|
||||
name: string
|
||||
up: (args: MigrateUpArgs) => Promise<void>
|
||||
}[]
|
||||
push: boolean
|
||||
rejectInitializing: () => void
|
||||
relationshipsSuffix?: string
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/drizzle",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "A library of shared functions used by different payload database adapters",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import type { Payload, PayloadRequest } from 'payload'
|
||||
|
||||
import { commitTransaction, initTransaction, killTransaction, readMigrationFiles } from 'payload'
|
||||
@@ -9,9 +8,12 @@ import type { DrizzleAdapter, Migration } from './types.js'
|
||||
import { migrationTableExists } from './utilities/migrationTableExists.js'
|
||||
import { parseError } from './utilities/parseError.js'
|
||||
|
||||
export async function migrate(this: DrizzleAdapter): Promise<void> {
|
||||
export const migrate: DrizzleAdapter['migrate'] = async function migrate(
|
||||
this: DrizzleAdapter,
|
||||
args,
|
||||
): Promise<void> {
|
||||
const { payload } = this
|
||||
const migrationFiles = await readMigrationFiles({ payload })
|
||||
const migrationFiles = args?.migrations || (await readMigrationFiles({ payload }))
|
||||
|
||||
if (!migrationFiles.length) {
|
||||
payload.logger.info({ msg: 'No migrations to run.' })
|
||||
@@ -64,7 +66,7 @@ export async function migrate(this: DrizzleAdapter): Promise<void> {
|
||||
|
||||
// If already ran, skip
|
||||
if (alreadyRan) {
|
||||
continue
|
||||
continue
|
||||
}
|
||||
|
||||
await runMigrationFile(payload, migration, newBatch)
|
||||
|
||||
@@ -42,7 +42,7 @@ export async function migrateFresh(
|
||||
|
||||
await this.dropDatabase({ adapter: this })
|
||||
|
||||
const migrationFiles = (await readMigrationFiles({ payload })) as Migration[]
|
||||
const migrationFiles = await readMigrationFiles({ payload })
|
||||
payload.logger.debug({
|
||||
msg: `Found ${migrationFiles.length} migration files.`,
|
||||
})
|
||||
|
||||
@@ -133,7 +133,7 @@ export type Migration = {
|
||||
db?: DrizzleTransaction | LibSQLDatabase<Record<string, never>> | PostgresDB
|
||||
payload: Payload
|
||||
req: PayloadRequest
|
||||
}) => Promise<boolean>
|
||||
}) => Promise<void>
|
||||
up: ({
|
||||
db,
|
||||
payload,
|
||||
@@ -142,7 +142,7 @@ export type Migration = {
|
||||
db?: DrizzleTransaction | LibSQLDatabase | PostgresDB
|
||||
payload: Payload
|
||||
req: PayloadRequest
|
||||
}) => Promise<boolean>
|
||||
}) => Promise<void>
|
||||
} & MigrationData
|
||||
|
||||
export type CreateJSONQueryArgs = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-nodemailer",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "Payload Nodemailer Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-resend",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "Payload Resend Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/graphql",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-react",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "The official React SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -41,8 +41,8 @@
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610"
|
||||
"react": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801"
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
|
||||
@@ -39,6 +39,9 @@ export const RefreshRouteOnSave: React.FC<{
|
||||
ready({
|
||||
serverURL,
|
||||
})
|
||||
|
||||
// refresh after the ready message is sent to get the latest data
|
||||
refresh()
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -46,7 +49,7 @@ export const RefreshRouteOnSave: React.FC<{
|
||||
window.removeEventListener('message', onMessage)
|
||||
}
|
||||
}
|
||||
}, [serverURL, onMessage, depth, apiRoute])
|
||||
}, [serverURL, onMessage, depth, apiRoute, refresh])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-vue",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "The official Vue SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "The official live preview JavaScript SDK for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/next",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -94,7 +94,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^16.8.1",
|
||||
"next": "^15.0.0-canary.53",
|
||||
"next": "^15.0.0-canary.104",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 2px;
|
||||
border-radius: var(--style-radius-s);
|
||||
background-color: var(--theme-elevation-50);
|
||||
opacity: 0;
|
||||
}
|
||||
@@ -51,6 +51,7 @@
|
||||
}
|
||||
|
||||
&--active {
|
||||
font-weight: 600;
|
||||
&::before {
|
||||
opacity: 1;
|
||||
background-color: var(--theme-elevation-100);
|
||||
@@ -78,14 +79,15 @@
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: calc(var(--base) / 2) calc(var(--base));
|
||||
line-height: base(1.2);
|
||||
padding: base(0.2) base(0.6);
|
||||
}
|
||||
|
||||
&__count {
|
||||
min-width: 22px;
|
||||
line-height: base(0.8);
|
||||
min-width: base(0.8);
|
||||
text-align: center;
|
||||
padding: 2px 7px;
|
||||
background-color: var(--theme-elevation-100);
|
||||
border-radius: 1px;
|
||||
border-radius: var(--style-radius-s);
|
||||
}
|
||||
}
|
||||
|
||||
103
packages/next/src/elements/EmailAndUsername/index.tsx
Normal file
103
packages/next/src/elements/EmailAndUsername/index.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
'use client'
|
||||
|
||||
import type { FieldPermissions, LoginWithUsernameOptions } from 'payload'
|
||||
|
||||
import { EmailField, RenderFields, TextField, useTranslation } from '@payloadcms/ui'
|
||||
import { email, username } from 'payload/shared'
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
loginWithUsername?: LoginWithUsernameOptions | false
|
||||
}
|
||||
function EmailFieldComponent(props: Props) {
|
||||
const { loginWithUsername } = props
|
||||
const { t } = useTranslation()
|
||||
|
||||
const requireEmail = !loginWithUsername || (loginWithUsername && loginWithUsername.requireEmail)
|
||||
const showEmailField =
|
||||
!loginWithUsername || loginWithUsername?.requireEmail || loginWithUsername?.allowEmailLogin
|
||||
|
||||
if (showEmailField) {
|
||||
return (
|
||||
<EmailField
|
||||
autoComplete="off"
|
||||
label={t('general:email')}
|
||||
name="email"
|
||||
path="email"
|
||||
required={requireEmail}
|
||||
validate={email}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function UsernameFieldComponent(props: Props) {
|
||||
const { loginWithUsername } = props
|
||||
const { t } = useTranslation()
|
||||
|
||||
const requireUsername = loginWithUsername && loginWithUsername.requireUsername
|
||||
const showUsernameField = Boolean(loginWithUsername)
|
||||
|
||||
if (showUsernameField) {
|
||||
return (
|
||||
<TextField
|
||||
label={t('authentication:username')}
|
||||
name="username"
|
||||
path="username"
|
||||
required={requireUsername}
|
||||
validate={username}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
type RenderEmailAndUsernameFieldsProps = {
|
||||
className?: string
|
||||
loginWithUsername?: LoginWithUsernameOptions | false
|
||||
operation?: 'create' | 'update'
|
||||
permissions?: {
|
||||
[fieldName: string]: FieldPermissions
|
||||
}
|
||||
readOnly: boolean
|
||||
}
|
||||
export function RenderEmailAndUsernameFields(props: RenderEmailAndUsernameFieldsProps) {
|
||||
const { className, loginWithUsername, operation, permissions, readOnly } = props
|
||||
|
||||
return (
|
||||
<RenderFields
|
||||
className={className}
|
||||
fieldMap={[
|
||||
{
|
||||
name: 'email',
|
||||
type: 'text',
|
||||
CustomField: <EmailFieldComponent loginWithUsername={loginWithUsername} />,
|
||||
cellComponentProps: null,
|
||||
fieldComponentProps: { type: 'email', autoComplete: 'off', readOnly },
|
||||
fieldIsPresentational: false,
|
||||
isFieldAffectingData: true,
|
||||
localized: false,
|
||||
},
|
||||
{
|
||||
name: 'username',
|
||||
type: 'text',
|
||||
CustomField: <UsernameFieldComponent loginWithUsername={loginWithUsername} />,
|
||||
cellComponentProps: null,
|
||||
fieldComponentProps: { type: 'text', readOnly },
|
||||
fieldIsPresentational: false,
|
||||
isFieldAffectingData: true,
|
||||
localized: false,
|
||||
},
|
||||
]}
|
||||
forceRender
|
||||
operation={operation}
|
||||
path=""
|
||||
permissions={permissions}
|
||||
readOnly={readOnly}
|
||||
schemaPath=""
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -37,10 +37,12 @@ const Component: React.FC<{
|
||||
<p>{t('general:changesNotSaved')}</p>
|
||||
</div>
|
||||
<div className={`${baseClass}__controls`}>
|
||||
<Button buttonStyle="secondary" onClick={onCancel}>
|
||||
<Button buttonStyle="secondary" onClick={onCancel} size="large">
|
||||
{t('general:stayOnThisPage')}
|
||||
</Button>
|
||||
<Button onClick={onConfirm}>{t('general:leaveAnyway')}</Button>
|
||||
<Button onClick={onConfirm} size="large">
|
||||
{t('general:leaveAnyway')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from '@payloadcms/ui'
|
||||
import { EntityType, formatAdminURL, groupNavItems } from '@payloadcms/ui/shared'
|
||||
import LinkWithDefault from 'next/link.js'
|
||||
import { usePathname } from 'next/navigation.js'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
const baseClass = 'nav'
|
||||
@@ -21,6 +22,7 @@ const baseClass = 'nav'
|
||||
export const DefaultNavClient: React.FC = () => {
|
||||
const { permissions } = useAuth()
|
||||
const { isEntityVisible } = useEntityVisibility()
|
||||
const pathname = usePathname()
|
||||
|
||||
const {
|
||||
collections,
|
||||
@@ -84,17 +86,11 @@ export const DefaultNavClient: React.FC = () => {
|
||||
LinkWithDefault) as typeof LinkWithDefault.default
|
||||
|
||||
const LinkElement = Link || 'a'
|
||||
|
||||
const activeCollection = window?.location?.pathname
|
||||
?.split('/')
|
||||
.find(
|
||||
(_, index, arr) =>
|
||||
arr[index - 1] === 'collections' || arr[index - 1] === 'globals',
|
||||
)
|
||||
const activeCollection = pathname.endsWith(href)
|
||||
|
||||
return (
|
||||
<LinkElement
|
||||
className={[`${baseClass}__link`, activeCollection === entity?.slug && `active`]
|
||||
className={[`${baseClass}__link`, activeCollection && `active`]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
href={href}
|
||||
@@ -102,9 +98,11 @@ export const DefaultNavClient: React.FC = () => {
|
||||
key={i}
|
||||
tabIndex={!navOpen ? -1 : undefined}
|
||||
>
|
||||
<span className={`${baseClass}__link-icon`}>
|
||||
<ChevronIcon direction="right" />
|
||||
</span>
|
||||
{activeCollection && (
|
||||
<span className={`${baseClass}__link-icon`}>
|
||||
<ChevronIcon direction="right" />
|
||||
</span>
|
||||
)}
|
||||
<span className={`${baseClass}__link-label`}>{entityLabel}</span>
|
||||
</LinkElement>
|
||||
)
|
||||
|
||||
@@ -110,16 +110,9 @@
|
||||
&__link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.active {
|
||||
.nav__link-icon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__link-icon {
|
||||
display: none;
|
||||
margin-right: calc(var(--base) * 0.25);
|
||||
top: -1px;
|
||||
position: relative;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { AcceptedLanguages, I18nClient } from '@payloadcms/translations'
|
||||
import type { SanitizedConfig } from 'payload'
|
||||
import type { PayloadRequest, SanitizedConfig } from 'payload'
|
||||
|
||||
import { initI18n, rtlLanguages } from '@payloadcms/translations'
|
||||
import { RootProvider } from '@payloadcms/ui'
|
||||
import '@payloadcms/ui/scss/app.scss'
|
||||
import { buildComponentMap } from '@payloadcms/ui/utilities/buildComponentMap'
|
||||
import { Merriweather } from 'next/font/google'
|
||||
import { headers as getHeaders, cookies as nextCookies } from 'next/headers.js'
|
||||
import { createClientConfig, parseCookies } from 'payload'
|
||||
import { createClientConfig, createLocalReq, parseCookies } from 'payload'
|
||||
import * as qs from 'qs-esm'
|
||||
import React from 'react'
|
||||
|
||||
import { getPayloadHMR } from '../../utilities/getPayloadHMR.js'
|
||||
@@ -16,14 +16,6 @@ import { getRequestTheme } from '../../utilities/getRequestTheme.js'
|
||||
import { DefaultEditView } from '../../views/Edit/Default/index.js'
|
||||
import { DefaultListView } from '../../views/List/Default/index.js'
|
||||
|
||||
const merriweather = Merriweather({
|
||||
display: 'swap',
|
||||
style: ['normal', 'italic'],
|
||||
subsets: ['latin'],
|
||||
variable: '--font-serif',
|
||||
weight: ['400', '900'],
|
||||
})
|
||||
|
||||
export const metadata = {
|
||||
description: 'Generated by Next.js',
|
||||
title: 'Next.js',
|
||||
@@ -61,6 +53,20 @@ export const RootLayout = async ({
|
||||
language: languageCode,
|
||||
})
|
||||
|
||||
const req = await createLocalReq(
|
||||
{
|
||||
fallbackLocale: null,
|
||||
req: {
|
||||
headers,
|
||||
host: headers.get('host'),
|
||||
i18n,
|
||||
url: `${payload.config.serverURL}`,
|
||||
} as PayloadRequest,
|
||||
},
|
||||
payload,
|
||||
)
|
||||
const { permissions, user } = await payload.auth({ headers, req })
|
||||
|
||||
const clientConfig = await createClientConfig({ config, t: i18n.t })
|
||||
|
||||
const dir = (rtlLanguages as unknown as AcceptedLanguages[]).includes(languageCode)
|
||||
@@ -100,7 +106,7 @@ export const RootLayout = async ({
|
||||
})
|
||||
|
||||
return (
|
||||
<html className={merriweather.variable} data-theme={theme} dir={dir} lang={languageCode}>
|
||||
<html data-theme={theme} dir={dir} lang={languageCode}>
|
||||
<body>
|
||||
<RootProvider
|
||||
componentMap={componentMap}
|
||||
@@ -109,9 +115,11 @@ export const RootLayout = async ({
|
||||
fallbackLang={clientConfig.i18n.fallbackLanguage}
|
||||
languageCode={languageCode}
|
||||
languageOptions={languageOptions}
|
||||
permissions={permissions}
|
||||
switchLanguageServerAction={switchLanguageServerAction}
|
||||
theme={theme}
|
||||
translations={i18n.translations}
|
||||
user={user}
|
||||
>
|
||||
{wrappedChildren}
|
||||
</RootProvider>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
@import './styles.scss';
|
||||
@import 'styles';
|
||||
@import './toasts.scss';
|
||||
@import './colors.scss';
|
||||
|
||||
:root {
|
||||
--base-px: 25;
|
||||
--base-px: 20;
|
||||
--base-body-size: 13;
|
||||
--base: calc((var(--base-px) / var(--base-body-size)) * 1rem);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
--theme-baseline-body-size: #{$baseline-body-size};
|
||||
--font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
|
||||
sans-serif;
|
||||
--font-serif: Georgia, 'Bitstream Charter', 'Charis SIL', Utopia, 'URW Bookman L', serif;
|
||||
--font-mono: monospace;
|
||||
|
||||
--style-radius-s: #{$style-radius-s};
|
||||
@@ -67,12 +68,6 @@ html {
|
||||
@extend %body;
|
||||
background: var(--theme-bg);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
opacity: 0;
|
||||
|
||||
&[data-theme='dark'],
|
||||
&[data-theme='light'] {
|
||||
opacity: initial;
|
||||
}
|
||||
|
||||
&[data-theme='dark'] {
|
||||
--theme-bg: var(--theme-elevation-0);
|
||||
@@ -111,12 +106,12 @@ body {
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--theme-success-500);
|
||||
background: var(--color-success-250);
|
||||
color: var(--theme-base-800);
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--theme-success-500);
|
||||
background: var(--color-success-250);
|
||||
color: var(--theme-base-800);
|
||||
}
|
||||
|
||||
|
||||
59
packages/next/src/scss/toastify.scss
Normal file
59
packages/next/src/scss/toastify.scss
Normal file
@@ -0,0 +1,59 @@
|
||||
@import 'vars';
|
||||
@import 'queries';
|
||||
|
||||
.Toastify {
|
||||
.Toastify__toast-container {
|
||||
left: base(5);
|
||||
transform: none;
|
||||
right: base(5);
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.Toastify__toast {
|
||||
padding: base(0.5);
|
||||
border-radius: $style-radius-m;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.Toastify__close-button {
|
||||
align-self: center;
|
||||
opacity: 0.7;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.Toastify__toast--success {
|
||||
color: var(--color-success-900);
|
||||
background: var(--color-success-500);
|
||||
|
||||
.Toastify__progress-bar {
|
||||
background-color: var(--color-success-900);
|
||||
}
|
||||
}
|
||||
|
||||
.Toastify__close-button--success {
|
||||
color: var(--color-success-900);
|
||||
}
|
||||
|
||||
.Toastify__toast--error {
|
||||
background: var(--theme-error-500);
|
||||
color: #fff;
|
||||
|
||||
.Toastify__progress-bar {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.Toastify__close-button--light {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
@include mid-break {
|
||||
.Toastify__toast-container {
|
||||
left: $baseline;
|
||||
right: $baseline;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,28 @@
|
||||
@import './styles.scss';
|
||||
|
||||
.payload-toast-container {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
.payload-toast-close-button {
|
||||
position: absolute;
|
||||
order: 3;
|
||||
left: unset;
|
||||
right: 0.5rem;
|
||||
top: 1.55rem;
|
||||
color: var(--theme-elevation-400);
|
||||
inset-inline-end: base(0.5);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--theme-elevation-600);
|
||||
background: unset;
|
||||
border: none;
|
||||
display: flex;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
background: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
width: base(0.75);
|
||||
height: base(0.75);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--theme-elevation-250);
|
||||
background: none;
|
||||
}
|
||||
|
||||
[dir='RTL'] & {
|
||||
@@ -27,16 +31,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
.toast-title {
|
||||
line-height: base(1);
|
||||
}
|
||||
|
||||
.payload-toast-item {
|
||||
padding: 1rem 2.5rem 1rem 1rem;
|
||||
color: var(--theme-text);
|
||||
padding: base(0.5);
|
||||
color: var(--theme-elevation-800);
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
border-radius: 0.15rem;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--theme-border-color);
|
||||
background: var(--theme-input-bg);
|
||||
box-shadow:
|
||||
@@ -45,6 +53,7 @@
|
||||
|
||||
.toast-content {
|
||||
transition: opacity 100ms cubic-bezier(0.55, 0.055, 0.675, 0.19);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&[data-front='false'] {
|
||||
@@ -60,51 +69,72 @@
|
||||
}
|
||||
|
||||
.toast-icon {
|
||||
svg {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
width: base(1);
|
||||
height: base(1);
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
& > * {
|
||||
width: base(1.2);
|
||||
height: base(1.2);
|
||||
}
|
||||
}
|
||||
|
||||
&.toast-warning {
|
||||
border-color: var(--theme-warning-200);
|
||||
background-color: var(--theme-warning-100);
|
||||
color: var(--theme-warning-800);
|
||||
border-color: var(--theme-warning-150);
|
||||
background-color: var(--theme-warning-50);
|
||||
|
||||
.payload-toast-close-button {
|
||||
color: var(--theme-warning-600);
|
||||
|
||||
&:hover {
|
||||
color: var(--theme-warning-250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.toast-error {
|
||||
border-color: var(--theme-error-300);
|
||||
background-color: var(--theme-error-150);
|
||||
color: var(--theme-error-800);
|
||||
border-color: var(--theme-error-150);
|
||||
background-color: var(--theme-error-50);
|
||||
|
||||
.payload-toast-close-button {
|
||||
color: var(--theme-error-600);
|
||||
|
||||
&:hover {
|
||||
color: var(--theme-error-250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.toast-success {
|
||||
border-color: var(--theme-success-200);
|
||||
background-color: var(--theme-success-100);
|
||||
color: var(--theme-success-800);
|
||||
border-color: var(--theme-success-150);
|
||||
background-color: var(--theme-success-50);
|
||||
|
||||
.payload-toast-close-button {
|
||||
color: var(--theme-success-600);
|
||||
|
||||
&:hover {
|
||||
color: var(--theme-success-250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.toast-info {
|
||||
border-color: var(--theme-elevation-250);
|
||||
background-color: var(--theme-elevation-100);
|
||||
}
|
||||
color: var(--theme-elevation-800);
|
||||
border-color: var(--theme-elevation-150);
|
||||
background-color: var(--theme-elevation-50);
|
||||
|
||||
[data-theme='light'] & {
|
||||
&.toast-warning {
|
||||
border-color: var(--theme-warning-550);
|
||||
background-color: var(--theme-warning-100);
|
||||
}
|
||||
.payload-toast-close-button {
|
||||
color: var(--theme-elevation-600);
|
||||
|
||||
&.toast-error {
|
||||
border-color: var(--theme-error-200);
|
||||
background-color: var(--theme-error-50);
|
||||
}
|
||||
|
||||
&.toast-success {
|
||||
border-color: var(--theme-success-550);
|
||||
background-color: var(--theme-success-50);
|
||||
}
|
||||
|
||||
&.toast-info {
|
||||
border-color: var(--theme-border-color);
|
||||
background-color: var(--theme-elevation-50);
|
||||
&:hover {
|
||||
color: var(--theme-elevation-250);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,17 +15,10 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
%jumbo {
|
||||
font-size: base(2.5);
|
||||
line-height: 1;
|
||||
margin: 0 0 base(2);
|
||||
}
|
||||
|
||||
%h1 {
|
||||
margin: 0 0 base(1);
|
||||
font-size: base(2);
|
||||
line-height: 1.15;
|
||||
letter-spacing: -1px;
|
||||
font-size: base(1.6);
|
||||
line-height: base(1.8);
|
||||
|
||||
@include small-break {
|
||||
letter-spacing: -0.5px;
|
||||
@@ -35,9 +28,8 @@
|
||||
|
||||
%h2 {
|
||||
margin: 0 0 base(1);
|
||||
font-size: base(1.25);
|
||||
line-height: 1.15;
|
||||
letter-spacing: -0.5px;
|
||||
font-size: base(1.3);
|
||||
line-height: base(1.6);
|
||||
|
||||
@include small-break {
|
||||
font-size: base(0.85);
|
||||
@@ -46,9 +38,8 @@
|
||||
|
||||
%h3 {
|
||||
margin: 0 0 base(1);
|
||||
font-size: base(0.925);
|
||||
line-height: 1.25;
|
||||
letter-spacing: -0.5px;
|
||||
font-size: base(1);
|
||||
line-height: base(1.2);
|
||||
|
||||
@include small-break {
|
||||
font-size: base(0.65);
|
||||
@@ -58,27 +49,27 @@
|
||||
|
||||
%h4 {
|
||||
margin: 0 0 $baseline;
|
||||
font-size: base(0.75);
|
||||
line-height: 1.5;
|
||||
font-size: base(0.8);
|
||||
line-height: base(1);
|
||||
letter-spacing: -0.375px;
|
||||
}
|
||||
|
||||
%h5 {
|
||||
margin: 0;
|
||||
font-size: base(0.5625);
|
||||
line-height: 1.5;
|
||||
font-size: base(0.65);
|
||||
line-height: base(0.8);
|
||||
}
|
||||
|
||||
%h6 {
|
||||
margin: 0;
|
||||
font-size: base(0.5);
|
||||
line-height: 1.5;
|
||||
font-size: base(0.6);
|
||||
line-height: base(0.8);
|
||||
}
|
||||
|
||||
%small {
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
|
||||
@@ -13,7 +13,7 @@ $breakpoint-l-width: 1440px !default;
|
||||
// BASELINE GRID
|
||||
//////////////////////////////
|
||||
|
||||
$baseline-px: 25px !default;
|
||||
$baseline-px: 20px !default;
|
||||
$baseline-body-size: 13px !default;
|
||||
$baseline: math.div($baseline-px, $baseline-body-size) + rem;
|
||||
|
||||
@@ -40,7 +40,7 @@ $color-purple: #f3ddf3 !default;
|
||||
|
||||
$style-radius-s: 3px !default;
|
||||
$style-radius-m: 4px !default;
|
||||
$style-radius-l: 9px !default;
|
||||
$style-radius-l: 8px !default;
|
||||
$style-stroke-width: 1px !default;
|
||||
|
||||
$style-stroke-width-s: 1px !default;
|
||||
@@ -50,8 +50,8 @@ $style-stroke-width-m: 2px !default;
|
||||
// MISC
|
||||
//////////////////////////////
|
||||
|
||||
$top-header-offset: calc(var(--base) - 1px);
|
||||
$top-header-offset-m: calc(var(--base) * 3);
|
||||
$top-header-offset: calc(base(1) - 1px);
|
||||
$top-header-offset-m: base(3);
|
||||
$focus-box-shadow: 0 0 0 $style-stroke-width-m var(--theme-success-500);
|
||||
|
||||
//////////////////////////////
|
||||
@@ -59,41 +59,19 @@ $focus-box-shadow: 0 0 0 $style-stroke-width-m var(--theme-success-500);
|
||||
//////////////////////////////
|
||||
|
||||
@mixin shadow-sm {
|
||||
box-shadow:
|
||||
0 2px 3px 0 rgba(0, 2, 4, 0.05),
|
||||
0 10px 4px -8px rgba(0, 2, 4, 0.02);
|
||||
box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@mixin shadow-m {
|
||||
box-shadow:
|
||||
0 0 30px 0 rgb(0 2 4 / 12%),
|
||||
0 30px 25px -8px rgb(0 2 4 / 10%);
|
||||
box-shadow: 0 4px 8px -3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@mixin shadow-lg {
|
||||
box-shadow:
|
||||
0 20px 35px -10px rgba(0, 2, 4, 0.2),
|
||||
0 6px 4px -4px rgba(0, 2, 4, 0.02);
|
||||
box-shadow: 0 -2px 16px -2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
@mixin shadow-lg-top {
|
||||
box-shadow:
|
||||
0 -20px 35px -10px rgba(0, 2, 4, 0.2),
|
||||
0 -6px 4px -4px rgba(0, 2, 4, 0.02);
|
||||
}
|
||||
|
||||
@mixin shadow {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.07);
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin inputShadowActive {
|
||||
box-shadow:
|
||||
0 2px 3px 0 rgba(0, 2, 4, 0.16),
|
||||
0 6px 4px -4px rgba(0, 2, 4, 0.13);
|
||||
box-shadow: 0 2px 16px -2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
@mixin inputShadow {
|
||||
@@ -101,15 +79,7 @@ $focus-box-shadow: 0 0 0 $style-stroke-width-m var(--theme-success-500);
|
||||
|
||||
&:not(:disabled) {
|
||||
&:hover {
|
||||
box-shadow:
|
||||
0 2px 3px 0 rgba(0, 2, 4, 0.13),
|
||||
0 6px 4px -4px rgba(0, 2, 4, 0.1);
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus-within,
|
||||
&:focus {
|
||||
@include inputShadowActive;
|
||||
box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,19 +117,33 @@ $focus-box-shadow: 0 0 0 $style-stroke-width-m var(--theme-success-500);
|
||||
@include blur-bg(var(--theme-bg), 0.3);
|
||||
}
|
||||
|
||||
@mixin readOnly {
|
||||
background: var(--theme-elevation-100);
|
||||
color: var(--theme-elevation-400);
|
||||
box-shadow: none;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--theme-elevation-150);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin formInput() {
|
||||
@include inputShadow;
|
||||
font-family: var(--font-body);
|
||||
width: 100%;
|
||||
border: 1px solid var(--theme-elevation-150);
|
||||
border-radius: var(--style-radius-s);
|
||||
background: var(--theme-input-bg);
|
||||
color: var(--theme-elevation-800);
|
||||
border-radius: 0;
|
||||
font-size: 1rem;
|
||||
height: base(2);
|
||||
line-height: base(1);
|
||||
padding: base(0.5) base(0.75);
|
||||
padding: base(0.4) base(0.75);
|
||||
-webkit-appearance: none;
|
||||
transition-property: border, box-shadow;
|
||||
transition-duration: 100ms;
|
||||
transition-timing-function: cubic-bezier(0, 0.2, 0.2, 1);
|
||||
|
||||
&[data-rtl='true'] {
|
||||
direction: rtl;
|
||||
@@ -189,12 +173,7 @@ $focus-box-shadow: 0 0 0 $style-stroke-width-m var(--theme-success-500);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: var(--theme-elevation-200);
|
||||
color: var(--theme-elevation-450);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--theme-elevation-150);
|
||||
}
|
||||
@include readOnly;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Metadata } from 'next'
|
||||
import type { Icon } from 'next/dist/lib/metadata/types/metadata-types.js'
|
||||
import type { MetaConfig } from 'payload'
|
||||
import type { IconConfig, MetaConfig } from 'payload'
|
||||
|
||||
import { payloadFaviconDark, payloadFaviconLight, staticOGImage } from '@payloadcms/ui/assets'
|
||||
import * as qs from 'qs-esm'
|
||||
@@ -24,7 +23,7 @@ export const meta = async (args: { serverURL: string } & MetaConfig): Promise<an
|
||||
titleSuffix,
|
||||
} = args
|
||||
|
||||
const payloadIcons: Icon[] = [
|
||||
const payloadIcons: IconConfig[] = [
|
||||
{
|
||||
type: 'image/png',
|
||||
rel: 'icon',
|
||||
@@ -40,10 +39,10 @@ export const meta = async (args: { serverURL: string } & MetaConfig): Promise<an
|
||||
},
|
||||
]
|
||||
|
||||
let icons = customIcons ?? payloadIcons // TODO: fix this type assertion
|
||||
let icons = payloadIcons
|
||||
|
||||
if (customIcons && typeof customIcons === 'object' && Array.isArray(customIcons)) {
|
||||
icons = payloadIcons.concat(customIcons) // TODO: fix this type assertion
|
||||
icons = customIcons
|
||||
}
|
||||
|
||||
const metaTitle = `${title} ${titleSuffix}`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { AdminViewProps, ServerSideEditViewProps } from 'payload'
|
||||
|
||||
import { DocumentInfoProvider, HydrateClientUser } from '@payloadcms/ui'
|
||||
import { DocumentInfoProvider, HydrateAuthProvider } from '@payloadcms/ui'
|
||||
import { RenderCustomComponent } from '@payloadcms/ui/shared'
|
||||
import { notFound } from 'next/navigation.js'
|
||||
import React from 'react'
|
||||
@@ -82,7 +82,7 @@ export const Account: React.FC<AdminViewProps> = async ({
|
||||
i18n={i18n}
|
||||
permissions={permissions}
|
||||
/>
|
||||
<HydrateClientUser permissions={permissions} user={user} />
|
||||
<HydrateAuthProvider permissions={permissions} />
|
||||
<RenderCustomComponent
|
||||
CustomComponent={
|
||||
typeof CustomAccountComponent === 'function' ? CustomAccountComponent : undefined
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import type { FormState } from 'payload'
|
||||
import type { FormState, LoginWithUsernameOptions } from 'payload'
|
||||
|
||||
import {
|
||||
ConfirmPasswordField,
|
||||
@@ -15,14 +15,13 @@ import {
|
||||
import { getFormState } from '@payloadcms/ui/shared'
|
||||
import React from 'react'
|
||||
|
||||
import { LoginField } from '../Login/LoginField/index.js'
|
||||
import { RenderEmailAndUsernameFields } from '../../elements/EmailAndUsername/index.js'
|
||||
|
||||
export const CreateFirstUserClient: React.FC<{
|
||||
initialState: FormState
|
||||
loginType: 'email' | 'emailOrUsername' | 'username'
|
||||
requireEmail?: boolean
|
||||
loginWithUsername?: LoginWithUsernameOptions | false
|
||||
userSlug: string
|
||||
}> = ({ initialState, loginType, requireEmail = true, userSlug }) => {
|
||||
}> = ({ initialState, loginWithUsername, userSlug }) => {
|
||||
const { getFieldMap } = useComponentMap()
|
||||
|
||||
const {
|
||||
@@ -58,11 +57,14 @@ export const CreateFirstUserClient: React.FC<{
|
||||
redirect={admin}
|
||||
validationOperation="create"
|
||||
>
|
||||
{['emailOrUsername', 'username'].includes(loginType) && <LoginField type="username" />}
|
||||
{['email', 'emailOrUsername'].includes(loginType) && (
|
||||
<LoginField required={requireEmail} type="email" />
|
||||
)}
|
||||
<RenderEmailAndUsernameFields
|
||||
className="emailAndUsername"
|
||||
loginWithUsername={loginWithUsername}
|
||||
operation="create"
|
||||
readOnly={false}
|
||||
/>
|
||||
<PasswordField
|
||||
autoComplete="off"
|
||||
label={t('authentication:newPassword')}
|
||||
name="password"
|
||||
path="password"
|
||||
@@ -77,7 +79,7 @@ export const CreateFirstUserClient: React.FC<{
|
||||
readOnly={false}
|
||||
schemaPath={userSlug}
|
||||
/>
|
||||
<FormSubmit>{t('general:create')}</FormSubmit>
|
||||
<FormSubmit size="large">{t('general:create')}</FormSubmit>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,3 +3,7 @@
|
||||
margin-bottom: var(--base);
|
||||
}
|
||||
}
|
||||
|
||||
.emailAndUsername {
|
||||
margin-bottom: var(--base);
|
||||
}
|
||||
|
||||
@@ -27,12 +27,6 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
|
||||
const collectionConfig = config.collections?.find((collection) => collection?.slug === userSlug)
|
||||
const { auth: authOptions } = collectionConfig
|
||||
const loginWithUsername = authOptions.loginWithUsername
|
||||
const emailRequired = loginWithUsername && loginWithUsername.requireEmail
|
||||
|
||||
let loginType: LoginFieldProps['type'] = loginWithUsername ? 'username' : 'email'
|
||||
if (loginWithUsername && (loginWithUsername.allowEmailLogin || loginWithUsername.requireEmail)) {
|
||||
loginType = 'emailOrUsername'
|
||||
}
|
||||
|
||||
const { formState } = await getDocumentData({
|
||||
collectionConfig,
|
||||
@@ -47,8 +41,7 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
|
||||
<p>{req.t('authentication:beginCreateFirstUser')}</p>
|
||||
<CreateFirstUserClient
|
||||
initialState={formState}
|
||||
loginType={loginType}
|
||||
requireEmail={emailRequired}
|
||||
loginWithUsername={loginWithUsername}
|
||||
userSlug={userSlug}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -26,13 +26,9 @@
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
gap: var(--gap);
|
||||
flex-wrap: wrap;
|
||||
|
||||
li {
|
||||
width: calc(100% / var(--cols) - var(--gap) / var(--cols) * (var(--cols) - 1));
|
||||
}
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--cols), 1fr);
|
||||
|
||||
.card {
|
||||
height: 100%;
|
||||
@@ -49,10 +45,18 @@
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
--cols: 1;
|
||||
--cols: 2;
|
||||
|
||||
&__wrap {
|
||||
gap: var(--base);
|
||||
}
|
||||
|
||||
&__card-list {
|
||||
gap: base(0.4);
|
||||
}
|
||||
}
|
||||
|
||||
@include extra-small-break {
|
||||
--cols: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { EntityToGroup } from '@payloadcms/ui/shared'
|
||||
import type { AdminViewProps } from 'payload'
|
||||
|
||||
import { HydrateClientUser } from '@payloadcms/ui'
|
||||
import { HydrateAuthProvider } from '@payloadcms/ui'
|
||||
import { EntityType, RenderCustomComponent, groupNavItems } from '@payloadcms/ui/shared'
|
||||
import LinkImport from 'next/link.js'
|
||||
import React, { Fragment } from 'react'
|
||||
@@ -79,7 +79,7 @@ export const Dashboard: React.FC<AdminViewProps> = ({ initPageResult, params, se
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<HydrateClientUser permissions={permissions} user={user} />
|
||||
<HydrateAuthProvider permissions={permissions} />
|
||||
<RenderCustomComponent
|
||||
CustomComponent={
|
||||
typeof CustomDashboardComponent === 'function' ? CustomDashboardComponent : undefined
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import type { AdminViewComponent, AdminViewProps, EditViewComponent } from 'payload'
|
||||
|
||||
import { DocumentInfoProvider, EditDepthProvider, HydrateClientUser } from '@payloadcms/ui'
|
||||
import { RenderCustomComponent, formatAdminURL , isEditing as getIsEditing } from '@payloadcms/ui/shared'
|
||||
import { DocumentInfoProvider, EditDepthProvider, HydrateAuthProvider } from '@payloadcms/ui'
|
||||
import {
|
||||
RenderCustomComponent,
|
||||
formatAdminURL,
|
||||
isEditing as getIsEditing,
|
||||
} from '@payloadcms/ui/shared'
|
||||
import { notFound, redirect } from 'next/navigation.js'
|
||||
import React from 'react'
|
||||
|
||||
@@ -208,7 +212,15 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
permissions={permissions}
|
||||
/>
|
||||
)}
|
||||
<HydrateClientUser permissions={permissions} user={user} />
|
||||
<HydrateAuthProvider permissions={permissions} />
|
||||
{/**
|
||||
* After bumping the Next.js canary to 104, and React to 19.0.0-rc-06d0b89e-20240801" we have to deepCopy the permissions object (https://github.com/payloadcms/payload/pull/7541).
|
||||
* If both HydrateClientUser and RenderCustomComponent receive the same permissions object (same object reference), we get a
|
||||
* "TypeError: Cannot read properties of undefined (reading '$$typeof')" error when loading up some version views - for example a versions
|
||||
* view in the draft-posts collection of the versions test suite. RenderCustomComponent is what renders the versions view.
|
||||
*
|
||||
* // TODO: Revisit this in the future and figure out why this is happening. Might be a React/Next.js bug. We don't know why it happens, and a future React/Next version might unbreak this (keep an eye on this and remove deepCopyObjectSimple if that's the case)
|
||||
*/}
|
||||
<EditDepthProvider
|
||||
depth={1}
|
||||
key={`${collectionSlug || globalSlug}${locale?.code ? `-${locale?.code}` : ''}`}
|
||||
|
||||
@@ -4,9 +4,7 @@ import {
|
||||
Button,
|
||||
CheckboxField,
|
||||
ConfirmPasswordField,
|
||||
EmailField,
|
||||
PasswordField,
|
||||
TextField,
|
||||
useAuth,
|
||||
useConfig,
|
||||
useDocumentInfo,
|
||||
@@ -14,12 +12,12 @@ import {
|
||||
useFormModified,
|
||||
useTranslation,
|
||||
} from '@payloadcms/ui'
|
||||
import { email as emailValidation } from 'payload/shared'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import type { Props } from './types.js'
|
||||
|
||||
import { RenderEmailAndUsernameFields } from '../../../../elements/EmailAndUsername/index.js'
|
||||
import { APIKey } from './APIKey.js'
|
||||
import './index.scss'
|
||||
|
||||
@@ -49,7 +47,7 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
const dispatchFields = useFormFields((reducer) => reducer[1])
|
||||
const modified = useFormModified()
|
||||
const { i18n, t } = useTranslation()
|
||||
const { isInitializing } = useDocumentInfo()
|
||||
const { docPermissions, isInitializing } = useDocumentInfo()
|
||||
|
||||
const {
|
||||
routes: { api },
|
||||
@@ -140,38 +138,12 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
<div className={[baseClass, className].filter(Boolean).join(' ')}>
|
||||
{!disableLocalStrategy && (
|
||||
<React.Fragment>
|
||||
{Boolean(loginWithUsername) && (
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
label={t('authentication:username')}
|
||||
name="username"
|
||||
readOnly={readOnly}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
{(!loginWithUsername ||
|
||||
loginWithUsername?.allowEmailLogin ||
|
||||
loginWithUsername?.requireEmail) && (
|
||||
<EmailField
|
||||
autoComplete="email"
|
||||
disabled={disabled}
|
||||
label={t('general:email')}
|
||||
name="email"
|
||||
readOnly={readOnly}
|
||||
required={!loginWithUsername || loginWithUsername?.requireEmail}
|
||||
validate={(value) =>
|
||||
emailValidation(value, {
|
||||
name: 'email',
|
||||
type: 'email',
|
||||
data: {},
|
||||
preferences: { fields: {} },
|
||||
req: { t } as any,
|
||||
required: true,
|
||||
siblingData: {},
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<RenderEmailAndUsernameFields
|
||||
loginWithUsername={loginWithUsername}
|
||||
operation={operation}
|
||||
permissions={docPermissions?.fields}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
{(showPasswordFields || requirePassword) && (
|
||||
<div className={`${baseClass}__changing-password`}>
|
||||
<PasswordField
|
||||
@@ -190,7 +162,7 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
buttonStyle="secondary"
|
||||
disabled={disabled}
|
||||
onClick={() => handleChangePassword(false)}
|
||||
size="small"
|
||||
size="medium"
|
||||
>
|
||||
{t('general:cancel')}
|
||||
</Button>
|
||||
@@ -201,7 +173,7 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
disabled={disabled}
|
||||
id="change-password"
|
||||
onClick={() => handleChangePassword(true)}
|
||||
size="small"
|
||||
size="medium"
|
||||
>
|
||||
{t('authentication:changePassword')}
|
||||
</Button>
|
||||
@@ -211,7 +183,7 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
buttonStyle="secondary"
|
||||
disabled={disabled}
|
||||
onClick={() => void unlock()}
|
||||
size="small"
|
||||
size="medium"
|
||||
>
|
||||
{t('authentication:forceUnlock')}
|
||||
</Button>
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
}
|
||||
|
||||
&__auth {
|
||||
margin-bottom: calc(var(--base) * 2);
|
||||
margin-top: calc(var(--base) * 0.5);
|
||||
margin-bottom: base(1.6);
|
||||
border-radius: var(--style-radius-s);
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
|
||||
@@ -103,7 +103,15 @@ export const DefaultEditView: React.FC = () => {
|
||||
const classes = [baseClass, id && `${baseClass}--is-editing`].filter(Boolean).join(' ')
|
||||
|
||||
const [schemaPath, setSchemaPath] = React.useState(entitySlug)
|
||||
const [validateBeforeSubmit, setValidateBeforeSubmit] = useState(false)
|
||||
const [validateBeforeSubmit, setValidateBeforeSubmit] = useState(() => {
|
||||
if (
|
||||
operation === 'create' &&
|
||||
collectionConfig.auth &&
|
||||
!collectionConfig.auth.disableLocalStrategy
|
||||
)
|
||||
return true
|
||||
return false
|
||||
})
|
||||
|
||||
const onSave = useCallback(
|
||||
(json) => {
|
||||
|
||||
@@ -51,7 +51,7 @@ export const ForgotPasswordView: React.FC<AdminViewProps> = ({ initPageResult })
|
||||
/>
|
||||
</p>
|
||||
<br />
|
||||
<Button Link={Link} buttonStyle="secondary" el="link" to={adminRoute}>
|
||||
<Button Link={Link} buttonStyle="secondary" el="link" size="large" to={adminRoute}>
|
||||
{i18n.t('general:backToDashboard')}
|
||||
</Button>
|
||||
</Fragment>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
.collection-list {
|
||||
width: 100%;
|
||||
margin-top: base(0.5);
|
||||
|
||||
&__wrap {
|
||||
padding-bottom: var(--spacing-view-bottom);
|
||||
@@ -14,7 +13,7 @@
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: base(0.75);
|
||||
|
||||
@@ -28,14 +27,12 @@
|
||||
|
||||
.pill {
|
||||
position: relative;
|
||||
top: -14px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__sub-header {
|
||||
flex-basis: 100%;
|
||||
margin-top: base(0.25);
|
||||
}
|
||||
|
||||
.table {
|
||||
@@ -57,7 +54,7 @@
|
||||
#heading-_select,
|
||||
.cell-_select {
|
||||
min-width: unset;
|
||||
width: auto;
|
||||
width: base(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { AdminViewProps, Where } from 'payload'
|
||||
|
||||
import {
|
||||
HydrateClientUser,
|
||||
HydrateAuthProvider,
|
||||
ListInfoProvider,
|
||||
ListQueryProvider,
|
||||
TableColumnsProvider,
|
||||
@@ -57,9 +57,23 @@ export const ListView: React.FC<AdminViewProps> = async ({
|
||||
req,
|
||||
user,
|
||||
where: {
|
||||
key: {
|
||||
equals: preferenceKey,
|
||||
},
|
||||
and: [
|
||||
{
|
||||
key: {
|
||||
equals: preferenceKey,
|
||||
},
|
||||
},
|
||||
{
|
||||
'user.relationTo': {
|
||||
equals: user.collection,
|
||||
},
|
||||
},
|
||||
{
|
||||
'user.value': {
|
||||
equals: user?.id,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
?.then((res) => res?.docs?.[0]?.value)) as ListPreferences
|
||||
@@ -124,7 +138,7 @@ export const ListView: React.FC<AdminViewProps> = async ({
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<HydrateClientUser permissions={permissions} user={user} />
|
||||
<HydrateAuthProvider permissions={permissions} />
|
||||
<ListInfoProvider
|
||||
collectionConfig={createClientCollectionConfig({
|
||||
collection: collectionConfig,
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
'use client'
|
||||
import type { PayloadRequest } from 'payload'
|
||||
import type { Validate, ValidateOptions } from 'payload'
|
||||
|
||||
import { EmailField, TextField, useConfig, useTranslation } from '@payloadcms/ui'
|
||||
import { EmailField, TextField, useTranslation } from '@payloadcms/ui'
|
||||
import { email, username } from 'payload/shared'
|
||||
import React from 'react'
|
||||
export type LoginFieldProps = {
|
||||
required?: boolean
|
||||
type: 'email' | 'emailOrUsername' | 'username'
|
||||
validate?: Validate
|
||||
}
|
||||
export const LoginField: React.FC<LoginFieldProps> = ({ type, required = true }) => {
|
||||
const { t } = useTranslation()
|
||||
const config = useConfig()
|
||||
|
||||
if (type === 'email') {
|
||||
return (
|
||||
@@ -20,17 +20,7 @@ export const LoginField: React.FC<LoginFieldProps> = ({ type, required = true })
|
||||
name="email"
|
||||
path="email"
|
||||
required={required}
|
||||
validate={(value) =>
|
||||
email(value, {
|
||||
name: 'email',
|
||||
type: 'email',
|
||||
data: {},
|
||||
preferences: { fields: {} },
|
||||
req: { t } as PayloadRequest,
|
||||
required: true,
|
||||
siblingData: {},
|
||||
})
|
||||
}
|
||||
validate={email}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -41,23 +31,8 @@ export const LoginField: React.FC<LoginFieldProps> = ({ type, required = true })
|
||||
label={t('authentication:username')}
|
||||
name="username"
|
||||
path="username"
|
||||
required
|
||||
validate={(value) =>
|
||||
username(value, {
|
||||
name: 'username',
|
||||
type: 'text',
|
||||
data: {},
|
||||
preferences: { fields: {} },
|
||||
req: {
|
||||
payload: {
|
||||
config,
|
||||
},
|
||||
t,
|
||||
} as PayloadRequest,
|
||||
required: true,
|
||||
siblingData: {},
|
||||
})
|
||||
}
|
||||
required={required}
|
||||
validate={username}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -68,36 +43,13 @@ export const LoginField: React.FC<LoginFieldProps> = ({ type, required = true })
|
||||
label={t('authentication:emailOrUsername')}
|
||||
name="username"
|
||||
path="username"
|
||||
required
|
||||
validate={(value) => {
|
||||
const passesUsername = username(value, {
|
||||
name: 'username',
|
||||
type: 'text',
|
||||
data: {},
|
||||
preferences: { fields: {} },
|
||||
req: {
|
||||
payload: {
|
||||
config,
|
||||
},
|
||||
t,
|
||||
} as PayloadRequest,
|
||||
required: true,
|
||||
siblingData: {},
|
||||
})
|
||||
const passesEmail = email(value, {
|
||||
name: 'username',
|
||||
type: 'email',
|
||||
data: {},
|
||||
preferences: { fields: {} },
|
||||
req: {
|
||||
payload: {
|
||||
config,
|
||||
},
|
||||
t,
|
||||
} as PayloadRequest,
|
||||
required: true,
|
||||
siblingData: {},
|
||||
})
|
||||
required={required}
|
||||
validate={(value, options) => {
|
||||
const passesUsername = username(value, options)
|
||||
const passesEmail = email(
|
||||
value,
|
||||
options as ValidateOptions<any, { username?: string }, any, any>,
|
||||
)
|
||||
|
||||
if (!passesEmail && !passesUsername) {
|
||||
return `${t('general:email')}: ${passesEmail} ${t('general:username')}: ${passesUsername}`
|
||||
|
||||
@@ -91,7 +91,7 @@ export const LoginForm: React.FC<{
|
||||
>
|
||||
{t('authentication:forgotPasswordQuestion')}
|
||||
</Link>
|
||||
<FormSubmit>{t('authentication:login')}</FormSubmit>
|
||||
<FormSubmit size="large">{t('authentication:login')}</FormSubmit>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export const LogoutClient: React.FC<{
|
||||
useEffect(() => {
|
||||
if (!isLoggingOut) {
|
||||
setIsLoggingOut(true)
|
||||
logOut()
|
||||
void logOut()
|
||||
}
|
||||
}, [isLoggingOut, logOut])
|
||||
|
||||
@@ -33,6 +33,7 @@ export const LogoutClient: React.FC<{
|
||||
Link={Link}
|
||||
buttonStyle="secondary"
|
||||
el="link"
|
||||
size="large"
|
||||
url={formatAdminURL({
|
||||
adminRoute,
|
||||
path: `/login${
|
||||
|
||||
@@ -38,7 +38,13 @@ export const NotFoundClient: React.FC<{
|
||||
<Gutter className={`${baseClass}__wrap`}>
|
||||
<h1>{t('general:nothingFound')}</h1>
|
||||
<p>{t('general:sorryNotFound')}</p>
|
||||
<Button Link={Link} className={`${baseClass}__button`} el="link" to={adminRoute}>
|
||||
<Button
|
||||
Link={Link}
|
||||
className={`${baseClass}__button`}
|
||||
el="link"
|
||||
size="large"
|
||||
to={adminRoute}
|
||||
>
|
||||
{t('general:backToDashboard')}
|
||||
</Button>
|
||||
</Gutter>
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { I18n } from '@payloadcms/translations'
|
||||
import type { Metadata } from 'next'
|
||||
import type { AdminViewComponent, SanitizedConfig } from 'payload'
|
||||
|
||||
import { HydrateClientUser } from '@payloadcms/ui'
|
||||
import { HydrateAuthProvider } from '@payloadcms/ui'
|
||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
@@ -58,21 +58,18 @@ export const NotFoundPage = async ({
|
||||
})
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<HydrateClientUser permissions={initPageResult.permissions} user={initPageResult.req.user} />
|
||||
<DefaultTemplate
|
||||
i18n={initPageResult.req.i18n}
|
||||
locale={initPageResult.locale}
|
||||
params={params}
|
||||
payload={initPageResult.req.payload}
|
||||
permissions={initPageResult.permissions}
|
||||
searchParams={searchParams}
|
||||
user={initPageResult.req.user}
|
||||
visibleEntities={initPageResult.visibleEntities}
|
||||
>
|
||||
<NotFoundClient />
|
||||
</DefaultTemplate>
|
||||
</Fragment>
|
||||
<DefaultTemplate
|
||||
i18n={initPageResult.req.i18n}
|
||||
locale={initPageResult.locale}
|
||||
params={params}
|
||||
payload={initPageResult.req.payload}
|
||||
permissions={initPageResult.permissions}
|
||||
searchParams={searchParams}
|
||||
user={initPageResult.req.user}
|
||||
visibleEntities={initPageResult.visibleEntities}
|
||||
>
|
||||
<NotFoundClient />
|
||||
</DefaultTemplate>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ export const ResetPasswordClient: React.FC<Args> = ({ token }) => {
|
||||
/>
|
||||
<ConfirmPasswordField />
|
||||
<HiddenField forceUsePathFromProps name="token" value={token} />
|
||||
<FormSubmit>{i18n.t('authentication:resetPassword')}</FormSubmit>
|
||||
<FormSubmit size="large">{i18n.t('authentication:resetPassword')}</FormSubmit>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,13 @@ export const UnauthorizedView: AdminViewComponent = ({ initPageResult }) => {
|
||||
<Gutter className={baseClass}>
|
||||
<h2>{i18n.t('error:unauthorized')}</h2>
|
||||
<p>{i18n.t('error:notAllowedToAccessPage')}</p>
|
||||
<Button Link={Link} className={`${baseClass}__button`} el="link" to={logoutRoute}>
|
||||
<Button
|
||||
Link={Link}
|
||||
className={`${baseClass}__button`}
|
||||
el="link"
|
||||
size="large"
|
||||
to={logoutRoute}
|
||||
>
|
||||
{i18n.t('authentication:logOut')}
|
||||
</Button>
|
||||
</Gutter>
|
||||
|
||||
@@ -81,7 +81,7 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
|
||||
|
||||
const canUpdate = docPermissions?.update?.permission
|
||||
|
||||
const localeValues = locales.map((locale) => locale.value)
|
||||
const localeValues = locales && locales.map((locale) => locale.value)
|
||||
|
||||
return (
|
||||
<main className={baseClass}>
|
||||
|
||||
@@ -26,7 +26,13 @@ const generateLabelFromValue = (
|
||||
locale: string,
|
||||
value: { relationTo: string; value: RelationshipValue } | RelationshipValue,
|
||||
): string => {
|
||||
let relation: string
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
.map((v) => generateLabelFromValue(collections, field, locale, v))
|
||||
.filter(Boolean) // Filters out any undefined or empty values
|
||||
.join(', ')
|
||||
}
|
||||
|
||||
let relatedDoc: RelationshipValue
|
||||
let valueToReturn = '' as any
|
||||
|
||||
@@ -37,17 +43,20 @@ const generateLabelFromValue = (
|
||||
return String(value)
|
||||
}
|
||||
|
||||
if (Array.isArray(relationTo)) {
|
||||
if (typeof value === 'object') {
|
||||
relation = value.relationTo
|
||||
relatedDoc = value.value
|
||||
}
|
||||
if (typeof value === 'object' && 'relationTo' in value) {
|
||||
relatedDoc = value.value
|
||||
} else {
|
||||
relation = relationTo
|
||||
// Non-polymorphic relationship
|
||||
relatedDoc = value
|
||||
}
|
||||
|
||||
const relatedCollection = collections.find((c) => c.slug === relation)
|
||||
const relatedCollection = relationTo
|
||||
? collections.find(
|
||||
(c) =>
|
||||
c.slug ===
|
||||
(typeof value === 'object' && 'relationTo' in value ? value.relationTo : relationTo),
|
||||
)
|
||||
: null
|
||||
|
||||
if (relatedCollection) {
|
||||
const useAsTitle = relatedCollection?.admin?.useAsTitle
|
||||
@@ -56,45 +65,65 @@ const generateLabelFromValue = (
|
||||
)
|
||||
let titleFieldIsLocalized = false
|
||||
|
||||
if (useAsTitleField && fieldAffectsData(useAsTitleField))
|
||||
if (useAsTitleField && fieldAffectsData(useAsTitleField)) {
|
||||
titleFieldIsLocalized = useAsTitleField.localized
|
||||
}
|
||||
|
||||
if (typeof relatedDoc?.[useAsTitle] !== 'undefined') {
|
||||
valueToReturn = relatedDoc[useAsTitle]
|
||||
} else if (typeof relatedDoc?.id !== 'undefined') {
|
||||
valueToReturn = relatedDoc.id
|
||||
} else {
|
||||
valueToReturn = relatedDoc
|
||||
}
|
||||
|
||||
if (typeof valueToReturn === 'object' && titleFieldIsLocalized) {
|
||||
valueToReturn = valueToReturn[locale]
|
||||
}
|
||||
} else if (relatedDoc) {
|
||||
// Handle non-polymorphic `hasMany` relationships or fallback
|
||||
if (typeof relatedDoc?.id !== 'undefined') {
|
||||
valueToReturn = relatedDoc.id
|
||||
} else {
|
||||
valueToReturn = relatedDoc
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof valueToReturn === 'object' && valueToReturn !== null) {
|
||||
valueToReturn = JSON.stringify(valueToReturn)
|
||||
}
|
||||
|
||||
return valueToReturn
|
||||
}
|
||||
|
||||
const Relationship: React.FC<Props> = ({ comparison, field, i18n, locale, version }) => {
|
||||
let placeholder = ''
|
||||
const placeholder = `[${i18n.t('general:noValue')}]`
|
||||
|
||||
const { collections } = useConfig()
|
||||
|
||||
if (version === comparison) placeholder = `[${i18n.t('general:noValue')}]`
|
||||
let versionToRender: string | undefined = placeholder
|
||||
let comparisonToRender: string | undefined = placeholder
|
||||
|
||||
let versionToRender = version
|
||||
let comparisonToRender = comparison
|
||||
if (version) {
|
||||
if ('hasMany' in field && field.hasMany && Array.isArray(version)) {
|
||||
versionToRender =
|
||||
version.map((val) => generateLabelFromValue(collections, field, locale, val)).join(', ') ||
|
||||
placeholder
|
||||
} else {
|
||||
versionToRender = generateLabelFromValue(collections, field, locale, version) || placeholder
|
||||
}
|
||||
}
|
||||
|
||||
if ('hasMany' in field && field.hasMany) {
|
||||
if (Array.isArray(version))
|
||||
versionToRender = version
|
||||
.map((val) => generateLabelFromValue(collections, field, locale, val))
|
||||
.join(', ')
|
||||
if (Array.isArray(comparison))
|
||||
comparisonToRender = comparison
|
||||
.map((val) => generateLabelFromValue(collections, field, locale, val))
|
||||
.join(', ')
|
||||
} else {
|
||||
versionToRender = generateLabelFromValue(collections, field, locale, version)
|
||||
comparisonToRender = generateLabelFromValue(collections, field, locale, comparison)
|
||||
if (comparison) {
|
||||
if ('hasMany' in field && field.hasMany && Array.isArray(comparison)) {
|
||||
comparisonToRender =
|
||||
comparison
|
||||
.map((val) => generateLabelFromValue(collections, field, locale, val))
|
||||
.join(', ') || placeholder
|
||||
} else {
|
||||
comparisonToRender =
|
||||
generateLabelFromValue(collections, field, locale, comparison) || placeholder
|
||||
}
|
||||
}
|
||||
|
||||
const label =
|
||||
@@ -112,10 +141,8 @@ const Relationship: React.FC<Props> = ({ comparison, field, i18n, locale, versio
|
||||
</Label>
|
||||
<ReactDiffViewer
|
||||
hideLineNumbers
|
||||
newValue={typeof versionToRender !== 'undefined' ? String(versionToRender) : placeholder}
|
||||
oldValue={
|
||||
typeof comparisonToRender !== 'undefined' ? String(comparisonToRender) : placeholder
|
||||
}
|
||||
newValue={versionToRender}
|
||||
oldValue={comparisonToRender}
|
||||
showDiffOnly={false}
|
||||
splitView
|
||||
styles={diffStyles}
|
||||
|
||||
@@ -18,12 +18,12 @@ export default {
|
||||
number: Text,
|
||||
point: Text,
|
||||
radio: Select,
|
||||
relationship: null,
|
||||
relationship: Relationship,
|
||||
richText: Text,
|
||||
row: Nested,
|
||||
select: Select,
|
||||
tabs: Tabs,
|
||||
text: Text,
|
||||
textarea: Text,
|
||||
upload: null,
|
||||
upload: Relationship,
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type {
|
||||
CollectionPermission,
|
||||
Document,
|
||||
EditViewComponent,
|
||||
GlobalPermission,
|
||||
OptionObject,
|
||||
} from 'payload'
|
||||
|
||||
import { notFound } from 'next/navigation.js'
|
||||
import {
|
||||
type CollectionPermission,
|
||||
type Document,
|
||||
type EditViewComponent,
|
||||
type GlobalPermission,
|
||||
type OptionObject,
|
||||
deepCopyObjectSimple,
|
||||
} from 'payload'
|
||||
import React from 'react'
|
||||
|
||||
import { getLatestVersion } from '../Versions/getLatestVersion.js'
|
||||
@@ -55,8 +55,18 @@ export const VersionView: EditViewComponent = async (props) => {
|
||||
})
|
||||
|
||||
if (collectionConfig?.versions?.drafts) {
|
||||
latestDraftVersion = await getLatestVersion(payload, slug, 'draft', 'collection')
|
||||
latestPublishedVersion = await getLatestVersion(payload, slug, 'published', 'collection')
|
||||
latestDraftVersion = await getLatestVersion({
|
||||
slug,
|
||||
type: 'collection',
|
||||
payload,
|
||||
status: 'draft',
|
||||
})
|
||||
latestPublishedVersion = await getLatestVersion({
|
||||
slug,
|
||||
type: 'collection',
|
||||
payload,
|
||||
status: 'published',
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
return notFound()
|
||||
@@ -80,8 +90,18 @@ export const VersionView: EditViewComponent = async (props) => {
|
||||
})
|
||||
|
||||
if (globalConfig?.versions?.drafts) {
|
||||
latestDraftVersion = await getLatestVersion(payload, slug, 'draft', 'global')
|
||||
latestPublishedVersion = await getLatestVersion(payload, slug, 'published', 'global')
|
||||
latestDraftVersion = await getLatestVersion({
|
||||
slug,
|
||||
type: 'global',
|
||||
payload,
|
||||
status: 'draft',
|
||||
})
|
||||
latestPublishedVersion = await getLatestVersion({
|
||||
slug,
|
||||
type: 'global',
|
||||
payload,
|
||||
status: 'published',
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
return notFound()
|
||||
@@ -116,7 +136,14 @@ export const VersionView: EditViewComponent = async (props) => {
|
||||
return (
|
||||
<DefaultVersionView
|
||||
doc={doc}
|
||||
docPermissions={docPermissions}
|
||||
/**
|
||||
* After bumping the Next.js canary to 104, and React to 19.0.0-rc-06d0b89e-20240801" we have to deepCopy the permissions object (https://github.com/payloadcms/payload/pull/7541).
|
||||
* If both HydrateClientUser and RenderCustomComponent receive the same permissions object (same object reference), we get a
|
||||
* "TypeError: Cannot read properties of undefined (reading '$$typeof')" error
|
||||
*
|
||||
* // TODO: Revisit this in the future and figure out why this is happening. Might be a React/Next.js bug. We don't know why it happens, and a future React/Next version might unbreak this (keep an eye on this and remove deepCopyObjectSimple if that's the case)
|
||||
*/
|
||||
docPermissions={deepCopyObjectSimple(docPermissions)}
|
||||
initialComparisonDoc={latestVersion}
|
||||
latestDraftVersion={latestDraftVersion?.id}
|
||||
latestPublishedVersion={latestPublishedVersion?.id}
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
export async function getLatestVersion(payload, slug, status, type = 'collection') {
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
type ReturnType = {
|
||||
id: string
|
||||
updatedAt: string
|
||||
} | null
|
||||
|
||||
type Args = {
|
||||
payload: Payload
|
||||
slug: string
|
||||
status: 'draft' | 'published'
|
||||
type: 'collection' | 'global'
|
||||
}
|
||||
export async function getLatestVersion(args: Args): Promise<ReturnType> {
|
||||
const { slug, type = 'collection', payload, status } = args
|
||||
|
||||
try {
|
||||
const sharedOptions = {
|
||||
depth: 0,
|
||||
@@ -22,11 +37,16 @@ export async function getLatestVersion(payload, slug, status, type = 'collection
|
||||
...sharedOptions,
|
||||
})
|
||||
|
||||
if (!response.docs.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
id: response.docs[0].id,
|
||||
updatedAt: response.docs[0].updatedAt,
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,13 +62,18 @@ export const VersionsView: EditViewComponent = async (props) => {
|
||||
},
|
||||
})
|
||||
if (collectionConfig?.versions?.drafts) {
|
||||
latestDraftVersion = await getLatestVersion(payload, collectionSlug, 'draft', 'collection')
|
||||
latestPublishedVersion = await getLatestVersion(
|
||||
latestDraftVersion = await getLatestVersion({
|
||||
slug: collectionSlug,
|
||||
type: 'collection',
|
||||
payload,
|
||||
collectionSlug,
|
||||
'published',
|
||||
'collection',
|
||||
)
|
||||
status: 'draft',
|
||||
})
|
||||
latestPublishedVersion = await getLatestVersion({
|
||||
slug: collectionSlug,
|
||||
type: 'collection',
|
||||
payload,
|
||||
status: 'published',
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error) // eslint-disable-line no-console
|
||||
@@ -90,8 +95,18 @@ export const VersionsView: EditViewComponent = async (props) => {
|
||||
})
|
||||
|
||||
if (globalConfig?.versions?.drafts) {
|
||||
latestDraftVersion = await getLatestVersion(payload, globalSlug, 'draft', 'global')
|
||||
latestPublishedVersion = await getLatestVersion(payload, globalSlug, 'published', 'global')
|
||||
latestDraftVersion = await getLatestVersion({
|
||||
slug: globalSlug,
|
||||
type: 'global',
|
||||
payload,
|
||||
status: 'draft',
|
||||
})
|
||||
latestPublishedVersion = await getLatestVersion({
|
||||
slug: globalSlug,
|
||||
type: 'global',
|
||||
payload,
|
||||
status: 'published',
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error) // eslint-disable-line no-console
|
||||
|
||||
@@ -18,16 +18,20 @@ export const withPayload = (nextConfig = {}) => {
|
||||
env: {
|
||||
...(nextConfig?.env || {}),
|
||||
},
|
||||
outputFileTracingExcludes: {
|
||||
...(nextConfig?.outputFileTracingExcludes || {}),
|
||||
'**/*': [
|
||||
...(nextConfig?.outputFileTracingExcludes?.['**/*'] || []),
|
||||
'drizzle-kit',
|
||||
'drizzle-kit/api',
|
||||
],
|
||||
},
|
||||
outputFileTracingIncludes: {
|
||||
...(nextConfig?.outputFileTracingIncludes || {}),
|
||||
'**/*': [...(nextConfig?.outputFileTracingIncludes?.['**/*'] || []), '@libsql/client'],
|
||||
},
|
||||
experimental: {
|
||||
...(nextConfig?.experimental || {}),
|
||||
outputFileTracingExcludes: {
|
||||
'**/*': [
|
||||
...(nextConfig.experimental?.outputFileTracingExcludes?.['**/*'] || []),
|
||||
'drizzle-kit',
|
||||
'drizzle-kit/payload',
|
||||
'libsql',
|
||||
],
|
||||
},
|
||||
turbo: {
|
||||
...(nextConfig?.experimental?.turbo || {}),
|
||||
resolveAlias: {
|
||||
@@ -63,9 +67,9 @@ export const withPayload = (nextConfig = {}) => {
|
||||
serverExternalPackages: [
|
||||
...(nextConfig?.serverExternalPackages || []),
|
||||
'drizzle-kit',
|
||||
'drizzle-kit/payload',
|
||||
'libsql',
|
||||
'drizzle-kit/api',
|
||||
'pino',
|
||||
'libsql',
|
||||
'pino-pretty',
|
||||
'graphql',
|
||||
],
|
||||
@@ -80,7 +84,7 @@ export const withPayload = (nextConfig = {}) => {
|
||||
externals: [
|
||||
...(incomingWebpackConfig?.externals || []),
|
||||
'drizzle-kit',
|
||||
'drizzle-kit/payload',
|
||||
'drizzle-kit/api',
|
||||
'sharp',
|
||||
'libsql',
|
||||
],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "3.0.0-beta.73",
|
||||
"version": "3.0.0-beta.75",
|
||||
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
|
||||
"keywords": [
|
||||
"admin panel",
|
||||
@@ -84,7 +84,7 @@
|
||||
"pretest": "pnpm build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@next/env": "^15.0.0-canary.53",
|
||||
"@next/env": "^15.0.0-canary.104",
|
||||
"@payloadcms/translations": "workspace:*",
|
||||
"@swc-node/core": "1.13.1",
|
||||
"@swc-node/sourcemap-support": "0.5.0",
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { SanitizedCollectionConfig, TypeWithID } from '../collections/confi
|
||||
import type { SanitizedConfig } from '../config/types.js'
|
||||
import type { Field, FieldAffectingData, RichTextField, Validate } from '../fields/config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../globals/config/types.js'
|
||||
import type { JsonObject, PayloadRequest, RequestContext } from '../types/index.js'
|
||||
import type { JsonObject, Payload, PayloadRequest, RequestContext } from '../types/index.js'
|
||||
import type { WithServerSidePropsComponentProps } from './elements/WithServerSideProps.js'
|
||||
|
||||
export type RichTextFieldProps<Value extends object, AdapterProps, ExtraFieldProperties = {}> = {
|
||||
@@ -189,6 +189,7 @@ type RichTextAdapterBase<
|
||||
WithServerSideProps: React.FC<Omit<WithServerSidePropsComponentProps, 'serverOnlyProps'>>
|
||||
config: SanitizedConfig
|
||||
i18n: I18nClient
|
||||
payload: Payload
|
||||
schemaPath: string
|
||||
}) => Map<string, React.ReactNode>
|
||||
generateSchemaMap?: (args: {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ArrayField } from '../../fields/config/types.js'
|
||||
import type { ArrayFieldValidation } from '../../fields/validations.js'
|
||||
import type { ErrorComponent } from '../forms/Error.js'
|
||||
import type { FieldMap } from '../forms/FieldMap.js'
|
||||
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
|
||||
@@ -12,8 +13,9 @@ export type ArrayFieldProps = {
|
||||
maxRows?: ArrayField['maxRows']
|
||||
minRows?: ArrayField['minRows']
|
||||
name?: string
|
||||
validate?: ArrayFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type ArrayFieldLabelComponent = LabelComponent<'array'>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Block, BlockField } from '../../fields/config/types.js'
|
||||
import type { BlockFieldValidation } from '../../fields/validations.js'
|
||||
import type { ErrorComponent } from '../forms/Error.js'
|
||||
import type { FieldMap } from '../forms/FieldMap.js'
|
||||
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
|
||||
@@ -12,8 +13,9 @@ export type BlocksFieldProps = {
|
||||
minRows?: number
|
||||
name?: string
|
||||
slug?: string
|
||||
validate?: BlockFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type ReducedBlock = {
|
||||
LabelComponent: Block['admin']['components']['Label']
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { CheckboxFieldValidation } from '../../fields/validations.js'
|
||||
import type { ErrorComponent } from '../forms/Error.js'
|
||||
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
|
||||
|
||||
@@ -9,8 +10,9 @@ export type CheckboxFieldProps = {
|
||||
onChange?: (val: boolean) => void
|
||||
partialChecked?: boolean
|
||||
path?: string
|
||||
validate?: CheckboxFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type CheckboxFieldLabelComponent = LabelComponent<'checkbox'>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { CodeField } from '../../fields/config/types.js'
|
||||
import type { CodeFieldValidation } from '../../fields/validations.js'
|
||||
import type { ErrorComponent } from '../forms/Error.js'
|
||||
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
|
||||
|
||||
@@ -7,8 +8,9 @@ export type CodeFieldProps = {
|
||||
language?: CodeField['admin']['language']
|
||||
name?: string
|
||||
path?: string
|
||||
validate?: CodeFieldValidation
|
||||
width: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type CodeFieldLabelComponent = LabelComponent<'code'>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { DateField } from '../../fields/config/types.js'
|
||||
import type { DateFieldValidation } from '../../fields/validations.js'
|
||||
import type { ErrorComponent } from '../forms/Error.js'
|
||||
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
|
||||
|
||||
@@ -7,8 +8,9 @@ export type DateFieldProps = {
|
||||
name?: string
|
||||
path?: string
|
||||
placeholder?: DateField['admin']['placeholder'] | string
|
||||
validate?: DateFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type DateFieldLabelComponent = LabelComponent<'date'>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { EmailField } from '../../fields/config/types.js'
|
||||
import type { EmailFieldValidation } from '../../fields/validations.js'
|
||||
import type { ErrorComponent } from '../forms/Error.js'
|
||||
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
|
||||
|
||||
@@ -7,8 +8,9 @@ export type EmailFieldProps = {
|
||||
name?: string
|
||||
path?: string
|
||||
placeholder?: EmailField['admin']['placeholder']
|
||||
validate?: EmailFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type EmailFieldLabelComponent = LabelComponent<'email'>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { JSONField } from '../../fields/config/types.js'
|
||||
import type { JSONFieldValidation } from '../../fields/validations.js'
|
||||
import type { ErrorComponent } from '../forms/Error.js'
|
||||
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
|
||||
|
||||
@@ -7,8 +8,9 @@ export type JSONFieldProps = {
|
||||
jsonSchema?: Record<string, unknown>
|
||||
name?: string
|
||||
path?: string
|
||||
validate?: JSONFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type JSONFieldLabelComponent = LabelComponent<'json'>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { NumberField } from '../../fields/config/types.js'
|
||||
import type { NumberFieldValidation } from '../../fields/validations.js'
|
||||
import type { ErrorComponent } from '../forms/Error.js'
|
||||
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
|
||||
|
||||
@@ -12,8 +13,9 @@ export type NumberFieldProps = {
|
||||
path?: string
|
||||
placeholder?: NumberField['admin']['placeholder']
|
||||
step?: number
|
||||
validate?: NumberFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type NumberFieldLabelComponent = LabelComponent<'number'>
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { PointFieldValidation } from '../../fields/validations.js'
|
||||
import type { ErrorComponent } from '../forms/Error.js'
|
||||
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
|
||||
|
||||
@@ -6,8 +7,9 @@ export type PointFieldProps = {
|
||||
path?: string
|
||||
placeholder?: string
|
||||
step?: number
|
||||
validate?: PointFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type PointFieldLabelComponent = LabelComponent<'point'>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Option } from '../../fields/config/types.js'
|
||||
import type { RadioFieldValidation } from '../../fields/validations.js'
|
||||
import type { ErrorComponent } from '../forms/Error.js'
|
||||
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
|
||||
|
||||
@@ -8,9 +9,10 @@ export type RadioFieldProps = {
|
||||
onChange?: OnChange
|
||||
options?: Option[]
|
||||
path?: string
|
||||
validate?: RadioFieldValidation
|
||||
value?: string
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type OnChange<T = string> = (value: T) => void
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { RelationshipField } from '../../fields/config/types.js'
|
||||
import type { RelationshipFieldValidation } from '../../fields/validations.js'
|
||||
import type { ErrorComponent } from '../forms/Error.js'
|
||||
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
|
||||
|
||||
@@ -9,8 +10,9 @@ export type RelationshipFieldProps = {
|
||||
name: string
|
||||
relationTo?: RelationshipField['relationTo']
|
||||
sortOptions?: RelationshipField['admin']['sortOptions']
|
||||
validate?: RelationshipFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type RelationshipFieldLabelComponent = LabelComponent<'relationship'>
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { RichTextFieldValidation } from '../../fields/validations.js'
|
||||
import type { ErrorComponent } from '../forms/Error.js'
|
||||
import type { MappedField } from '../forms/FieldMap.js'
|
||||
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
|
||||
@@ -5,8 +6,9 @@ import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../typ
|
||||
export type RichTextComponentProps = {
|
||||
name: string
|
||||
richTextComponentMap?: Map<string, MappedField[] | React.ReactNode>
|
||||
validate?: RichTextFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type RichTextFieldLabelComponent = LabelComponent<'richText'>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Option } from '../../fields/config/types.js'
|
||||
import type { SelectFieldValidation } from '../../fields/validations.js'
|
||||
import type { ErrorComponent } from '../forms/Error.js'
|
||||
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
|
||||
|
||||
@@ -10,9 +11,10 @@ export type SelectFieldProps = {
|
||||
onChange?: (e: string | string[]) => void
|
||||
options?: Option[]
|
||||
path?: string
|
||||
validate?: SelectFieldValidation
|
||||
value?: string
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type SelectFieldLabelComponent = LabelComponent<'select'>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { TextField } from '../../fields/config/types.js'
|
||||
import type { TextFieldValidation } from '../../fields/validations.js'
|
||||
import type { ErrorComponent } from '../forms/Error.js'
|
||||
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
|
||||
|
||||
@@ -13,8 +14,9 @@ export type TextFieldProps = {
|
||||
onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>
|
||||
path?: string
|
||||
placeholder?: TextField['admin']['placeholder']
|
||||
validate?: TextFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type TextFieldLabelComponent = LabelComponent<'text'>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { TextareaField } from '../../fields/config/types.js'
|
||||
import type { TextareaFieldValidation } from '../../fields/validations.js'
|
||||
import type { ErrorComponent } from '../forms/Error.js'
|
||||
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
|
||||
|
||||
@@ -9,8 +10,9 @@ export type TextareaFieldProps = {
|
||||
path?: string
|
||||
placeholder?: TextareaField['admin']['placeholder']
|
||||
rows?: number
|
||||
validate?: TextareaFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type TextareaFieldLabelComponent = LabelComponent<'textarea'>
|
||||
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import type { DescriptionComponent, FormFieldBase, LabelComponent, UploadField } from 'payload'
|
||||
import type {
|
||||
DescriptionComponent,
|
||||
FormFieldBase,
|
||||
LabelComponent,
|
||||
UploadField,
|
||||
UploadFieldValidation,
|
||||
} from 'payload'
|
||||
|
||||
import type { ErrorComponent } from '../forms/Error.js'
|
||||
|
||||
@@ -7,8 +13,9 @@ export type UploadFieldProps = {
|
||||
name?: string
|
||||
path?: string
|
||||
relationTo?: UploadField['relationTo']
|
||||
validate?: UploadFieldValidation
|
||||
width?: string
|
||||
} & FormFieldBase
|
||||
} & Omit<FormFieldBase, 'validate'>
|
||||
|
||||
export type UploadFieldLabelComponent = LabelComponent<'upload'>
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user