Compare commits
13 Commits
v1
...
db-postgre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c39472259a | ||
|
|
e2d36c3cab | ||
|
|
0e682a32c3 | ||
|
|
266c3274d0 | ||
|
|
67b3baaa44 | ||
|
|
55659c7c36 | ||
|
|
6a0a859563 | ||
|
|
57da3c99a7 | ||
|
|
611438177b | ||
|
|
d068ef7e24 | ||
|
|
7a9af4417a | ||
|
|
8d14c213c8 | ||
|
|
182c57b191 |
21
.github/actions/restore-build-cache/action.yml
vendored
21
.github/actions/restore-build-cache/action.yml
vendored
@@ -1,21 +0,0 @@
|
||||
name: Restore build cache
|
||||
description: Installes node, pnpm, and restores the build cache
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
82
.github/workflows/main.yml
vendored
82
.github/workflows/main.yml
vendored
@@ -92,8 +92,22 @@ jobs:
|
||||
POSTGRES_DB: payloadtests
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/restore-build-cache@v1
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Start PostgreSQL
|
||||
uses: CasperWA/postgresql-action@v1.2
|
||||
@@ -128,11 +142,25 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
part: [1/4, 2/4, 3/4, 4/4]
|
||||
part: [1/8, 2/8, 3/8, 4/8, 5/8, 6/8, 7/8, 8/8]
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/restore-build-cache@v1
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: E2E Tests
|
||||
uses: nick-fields/retry@v2
|
||||
@@ -154,8 +182,22 @@ jobs:
|
||||
needs: core-build
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/restore-build-cache@v1
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Generate Payload Types
|
||||
run: pnpm dev:generate-types fields
|
||||
@@ -180,8 +222,22 @@ jobs:
|
||||
- live-preview-react
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/restore-build-cache@v1
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Build ${{ matrix.pkg }}
|
||||
run: pnpm turbo run build --filter=${{ matrix.pkg }}
|
||||
@@ -202,8 +258,22 @@ jobs:
|
||||
- plugin-sentry
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/restore-build-cache@v1
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Build ${{ matrix.pkg }}
|
||||
run: pnpm turbo run build --filter=${{ matrix.pkg }}
|
||||
|
||||
22
CHANGELOG.md
22
CHANGELOG.md
@@ -1,3 +1,25 @@
|
||||
## [2.1.0](https://github.com/payloadcms/payload/compare/v2.0.15...v2.1.0) (2023-11-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add internationalization (i18n) to locales ([#4005](https://github.com/payloadcms/payload/issues/4005)) ([6a0a859](https://github.com/payloadcms/payload/commit/6a0a859563ed9e742260ea51a1839a1ef0f61fce))
|
||||
* Custom Error, Label, and before/after field components ([#3747](https://github.com/payloadcms/payload/issues/3747)) ([266c327](https://github.com/payloadcms/payload/commit/266c3274d03e4fd52c692eeef1ee9248dcf66189))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* error on graphql multiple queries ([#3985](https://github.com/payloadcms/payload/issues/3985)) ([57da3c9](https://github.com/payloadcms/payload/commit/57da3c99a7e4ce5d3d1e17315e3691815f363704))
|
||||
* focal and cropping issues, adds test ([#4039](https://github.com/payloadcms/payload/issues/4039)) ([acba5e4](https://github.com/payloadcms/payload/commit/acba5e482b7ddc6e3dc6ba9b7736022770d69a55))
|
||||
* handle invalid tokens in refresh token operation ([#3647](https://github.com/payloadcms/payload/issues/3647)) ([131d89c](https://github.com/payloadcms/payload/commit/131d89c3f50c237e1ab2d7cd32d7a8226a9f8ce3))
|
||||
* hasMany number and select fields unable to save within arrays ([#4047](https://github.com/payloadcms/payload/issues/4047)) ([182c57b](https://github.com/payloadcms/payload/commit/182c57b191010ce3dcf659f39c1dc2f7cf80662e))
|
||||
* injects array and block ids into fieldSchemaToJSON ([#4043](https://github.com/payloadcms/payload/issues/4043)) ([d068ef7](https://github.com/payloadcms/payload/commit/d068ef7e2483d49dc41bdd7735042ddcaa0a684c))
|
||||
* parse predefined migrations via file arg or name prefix ([#4001](https://github.com/payloadcms/payload/issues/4001)) ([eb42c03](https://github.com/payloadcms/payload/commit/eb42c031ef980558ed051d4163925aa28d6ab090))
|
||||
* polymorphic hasMany relationships missing in postgres admin ([#4053](https://github.com/payloadcms/payload/issues/4053)) ([7a9af44](https://github.com/payloadcms/payload/commit/7a9af4417a56c621f01195f9a2904b9adffaad7a))
|
||||
* resets list filter row when the filter on field is changed ([#3956](https://github.com/payloadcms/payload/issues/3956)) ([8d14c21](https://github.com/payloadcms/payload/commit/8d14c213c878a1afda2b3bf03431fed5aa2a44e3))
|
||||
* Update API Views ([b008b6c](https://github.com/payloadcms/payload/commit/b008b6c6463c9dc3d8e61eaa0a9210aa1a189442))
|
||||
* vite not replacing env vars correctly when building ([67b3baa](https://github.com/payloadcms/payload/commit/67b3baaa445a13246be8178d57eaeba92888bef1))
|
||||
|
||||
## [2.0.15](https://github.com/payloadcms/payload/compare/v2.0.14...v2.0.15) (2023-11-03)
|
||||
|
||||
|
||||
|
||||
@@ -432,6 +432,15 @@ All Payload fields support the ability to swap in your own React components. So,
|
||||
| **`Cell`** | Used in the `List` view's table to represent a table-based preview of the data stored in the field. [More](#cell-component) |
|
||||
| **`Field`** | Swap out the field itself within all `Edit` views. [More](#field-component) |
|
||||
|
||||
As an alternative to replacing the entire Field component, you may want to keep the majority of the default Field component and only swap components within. This allows you to replace the **`Label`** or **`Error`** within a field component or add additional components inside the field with **`BeforeInput`** or **`AfterInput`**. **`BeforeInput`** and **`AfterInput`** are allowed in any fields that don't contain other fields, except [UI](/docs/fields/ui) and [Rich Text](/docs/fields/rich-text).
|
||||
|
||||
| Component | Description |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| **`Label`** | Override the default Label in the Field Component. [More](#label-component) |
|
||||
| **`Error`** | Override the default Label in the Field Component. [More](#error-component) |
|
||||
| **`BeforeInput`** | An array of elements that will be added before `input`/`textarea` elements. [More](#afterinput-and-beforeinput) |
|
||||
| **`AfterInput`** | An array of elements that will be added after `input`/`textarea` elements. [More](#afterinput-and-beforeinput) |
|
||||
|
||||
## Cell Component
|
||||
|
||||
These are the props that will be passed to your custom Cell to use in your own components.
|
||||
@@ -487,6 +496,103 @@ const CustomTextField: React.FC<Props> = ({ path }) => {
|
||||
components, including the <strong>useField</strong> hook, [click here](/docs/admin/hooks).
|
||||
</Banner>
|
||||
|
||||
## Label Component
|
||||
|
||||
These are the props that will be passed to your custom Label.
|
||||
|
||||
| Property | Description |
|
||||
| ---------------- | ---------------------------------------------------------------- |
|
||||
| **`htmlFor`** | Property used to set `for` attribute for label. |
|
||||
| **`label`** | Label value provided in field, it can be used with i18n. |
|
||||
| **`required`** | A boolean value that represents if the field is required or not. |
|
||||
|
||||
#### Example
|
||||
|
||||
```tsx
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { getTranslation } from 'payload/utilities/getTranslation'
|
||||
|
||||
type Props = {
|
||||
htmlFor?: string
|
||||
label?: Record<string, string> | false | string
|
||||
required?: boolean
|
||||
}
|
||||
|
||||
const CustomLabel: React.FC<Props> = (props) => {
|
||||
const { htmlFor, label, required = false } = props
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
if (label) {
|
||||
return (<span>
|
||||
{getTranslation(label, i18n)}
|
||||
{required && <span className="required">*</span>}
|
||||
</span>);
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
## Error Component
|
||||
|
||||
These are the props that will be passed to your custom Error.
|
||||
|
||||
| Property | Description |
|
||||
| ---------------- | ------------------------------------------------------------- |
|
||||
| **`message`** | The error message. |
|
||||
| **`showError`** | A boolean value that represents if the error should be shown. |
|
||||
|
||||
#### Example
|
||||
|
||||
```tsx
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
message: string
|
||||
showError?: boolean
|
||||
}
|
||||
|
||||
const CustomError: React.FC<Props> = (props) => {
|
||||
const { message, showError } = props
|
||||
|
||||
if (showError) {
|
||||
return <p style={{color: 'red'}}>{message}</p>
|
||||
} else return null;
|
||||
}
|
||||
```
|
||||
|
||||
## AfterInput and BeforeInput
|
||||
|
||||
With these properties you can add multiple components before and after the input element. For example, you can add an absolutely positioned button to clear the current field value.
|
||||
|
||||
#### Example
|
||||
|
||||
```tsx
|
||||
import React from 'react'
|
||||
import './style.scss'
|
||||
|
||||
const ClearButton: React.FC = () => {
|
||||
return <button onClick={() => {/* ... */}}>X</button>
|
||||
}
|
||||
|
||||
const fieldField: Field = {
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
admin: {
|
||||
components: {
|
||||
AfterInput: [
|
||||
<ClearButton />
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default titleField;
|
||||
```
|
||||
|
||||
## Custom providers
|
||||
|
||||
As your admin customizations gets more complex you may want to share state between fields or other components. You can add custom providers to do add your own context to any Payload app for use in other custom components within the admin panel. Within your config add `admin.components.providers`, these can be used to share context or provide other custom functionality. Read the [React context](https://reactjs.org/docs/context.html) docs to learn more.
|
||||
|
||||
@@ -758,3 +758,29 @@ const MyComponent: React.FC = () => {
|
||||
### usePreferences
|
||||
|
||||
Returns methods to set and get user preferences. More info can be found [here](https://payloadcms.com/docs/admin/preferences).
|
||||
|
||||
### useTableColumns
|
||||
|
||||
Returns methods to manipulate table columns
|
||||
|
||||
```tsx
|
||||
import { useTableColumns } from 'payload/components/utilities'
|
||||
|
||||
const MyComponent: React.FC = () => {
|
||||
// highlight-start
|
||||
const { setActiveColumns } = useTableColumns()
|
||||
|
||||
const resetColumns = () => {
|
||||
setActiveColumns(['id', 'createdAt', 'updatedAt'])
|
||||
}
|
||||
// highlight-end
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={resetColumns}
|
||||
>
|
||||
Reset columns
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -57,6 +57,38 @@ export default buildConfig({
|
||||
})
|
||||
```
|
||||
|
||||
**Example Payload config set up for localization with full locales objects (including [internationalization](/docs/configuration/i18n) support):**
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
|
||||
export default buildConfig({
|
||||
collections: [
|
||||
// collections go here
|
||||
],
|
||||
localization: {
|
||||
locales: [
|
||||
{
|
||||
label: {
|
||||
en: 'English', // English label
|
||||
nb: 'Engelsk', // Norwegian label
|
||||
},
|
||||
code: 'en',
|
||||
},
|
||||
{
|
||||
label: {
|
||||
en: 'Norwegian', // English label
|
||||
nb: 'Norsk', // Norwegian label
|
||||
},
|
||||
code: 'nb',
|
||||
},
|
||||
],
|
||||
defaultLocale: 'en',
|
||||
fallback: true,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Here is a brief explanation of each of the options available within the `localization` property:**
|
||||
|
||||
**`locales`**
|
||||
|
||||
@@ -63,7 +63,6 @@ export const getViteConfig = async (payloadConfig: SanitizedConfig): Promise<Inl
|
||||
'module.hot': 'undefined',
|
||||
'process.argv': '[]',
|
||||
'process.cwd': 'function () { return "/" }',
|
||||
'process.env': '{}',
|
||||
'process?.cwd': 'function () { return "/" }',
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.7",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -4,6 +4,7 @@ export const commitTransaction: CommitTransaction = async function commitTransac
|
||||
if (!this.sessions[id]?.inTransaction()) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.sessions[id].commitTransaction()
|
||||
await this.sessions[id].endSession()
|
||||
delete this.sessions[id]
|
||||
|
||||
@@ -3,10 +3,20 @@ import type { RollbackTransaction } from 'payload/database'
|
||||
export const rollbackTransaction: RollbackTransaction = async function rollbackTransaction(
|
||||
id = '',
|
||||
) {
|
||||
if (!this.sessions[id]?.inTransaction()) {
|
||||
this.payload.logger.warn('rollbackTransaction called when no transaction exists')
|
||||
// if multiple operations are using the same transaction, the first will flow through and delete the session.
|
||||
// subsequent calls should be ignored.
|
||||
if (!this.sessions[id]) {
|
||||
return
|
||||
}
|
||||
|
||||
// when session exists but is not inTransaction something unexpected is happening to the session
|
||||
if (!this.sessions[id].inTransaction()) {
|
||||
this.payload.logger.warn('rollbackTransaction called when no transaction exists')
|
||||
delete this.sessions[id]
|
||||
return
|
||||
}
|
||||
|
||||
// the first call for rollback should be aborted and deleted causing any other operations with the same transaction to fail
|
||||
await this.sessions[id].abortTransaction()
|
||||
await this.sessions[id].endSession()
|
||||
delete this.sessions[id]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "0.1.12",
|
||||
"version": "0.1.13",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -23,6 +23,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
||||
|
||||
buildTable({
|
||||
adapter: this,
|
||||
buildNumbers: true,
|
||||
buildRelationships: true,
|
||||
disableNotNull: !!collection?.versions?.drafts,
|
||||
disableUnique: false,
|
||||
@@ -37,6 +38,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
||||
|
||||
buildTable({
|
||||
adapter: this,
|
||||
buildNumbers: true,
|
||||
buildRelationships: true,
|
||||
disableNotNull: !!collection.versions?.drafts,
|
||||
disableUnique: true,
|
||||
@@ -52,6 +54,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
||||
|
||||
buildTable({
|
||||
adapter: this,
|
||||
buildNumbers: true,
|
||||
buildRelationships: true,
|
||||
disableNotNull: !!global?.versions?.drafts,
|
||||
disableUnique: false,
|
||||
@@ -66,6 +69,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
||||
|
||||
buildTable({
|
||||
adapter: this,
|
||||
buildNumbers: true,
|
||||
buildRelationships: true,
|
||||
disableNotNull: !!global.versions?.drafts,
|
||||
disableUnique: true,
|
||||
|
||||
@@ -26,6 +26,7 @@ type Args = {
|
||||
adapter: PostgresAdapter
|
||||
baseColumns?: Record<string, PgColumnBuilder>
|
||||
baseExtraConfig?: Record<string, (cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder>
|
||||
buildNumbers?: boolean
|
||||
buildRelationships?: boolean
|
||||
disableNotNull: boolean
|
||||
disableUnique: boolean
|
||||
@@ -39,6 +40,7 @@ type Args = {
|
||||
}
|
||||
|
||||
type Result = {
|
||||
hasManyNumberField: 'index' | boolean
|
||||
relationsToBuild: Map<string, string>
|
||||
}
|
||||
|
||||
@@ -46,6 +48,7 @@ export const buildTable = ({
|
||||
adapter,
|
||||
baseColumns = {},
|
||||
baseExtraConfig = {},
|
||||
buildNumbers,
|
||||
buildRelationships,
|
||||
disableNotNull,
|
||||
disableUnique = false,
|
||||
@@ -53,10 +56,11 @@ export const buildTable = ({
|
||||
rootRelationsToBuild,
|
||||
rootRelationships,
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
rootTableName: incomingRootTableName,
|
||||
tableName,
|
||||
timestamps,
|
||||
}: Args): Result => {
|
||||
const rootTableName = incomingRootTableName || tableName
|
||||
const columns: Record<string, PgColumnBuilder> = baseColumns
|
||||
const indexes: Record<string, (cols: GenericColumns) => IndexBuilder> = {}
|
||||
|
||||
@@ -102,6 +106,7 @@ export const buildTable = ({
|
||||
hasManyNumberField,
|
||||
} = traverseFields({
|
||||
adapter,
|
||||
buildNumbers,
|
||||
buildRelationships,
|
||||
columns,
|
||||
disableNotNull,
|
||||
@@ -116,7 +121,7 @@ export const buildTable = ({
|
||||
relationships,
|
||||
rootRelationsToBuild: rootRelationsToBuild || relationsToBuild,
|
||||
rootTableIDColType: rootTableIDColType || idColType,
|
||||
rootTableName: rootTableName || tableName,
|
||||
rootTableName,
|
||||
}))
|
||||
|
||||
if (timestamps) {
|
||||
@@ -185,8 +190,8 @@ export const buildTable = ({
|
||||
adapter.relations[`relations_${localeTableName}`] = localesTableRelations
|
||||
}
|
||||
|
||||
if (hasManyNumberField) {
|
||||
const numbersTableName = `${tableName}_numbers`
|
||||
if (hasManyNumberField && buildNumbers) {
|
||||
const numbersTableName = `${rootTableName}_numbers`
|
||||
const columns: Record<string, PgColumnBuilder> = {
|
||||
id: serial('id').primaryKey(),
|
||||
number: numeric('number'),
|
||||
@@ -327,5 +332,5 @@ export const buildTable = ({
|
||||
|
||||
adapter.relations[`relations_${tableName}`] = tableRelations
|
||||
|
||||
return { relationsToBuild }
|
||||
return { hasManyNumberField, relationsToBuild }
|
||||
}
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { Relation } from 'drizzle-orm'
|
||||
import { relations } from 'drizzle-orm'
|
||||
import type { IndexBuilder, PgColumnBuilder, UniqueConstraintBuilder } from 'drizzle-orm/pg-core'
|
||||
import type { Field, TabAsField } from 'payload/types'
|
||||
|
||||
import { relations } from 'drizzle-orm'
|
||||
import {
|
||||
PgNumericBuilder,
|
||||
PgVarcharBuilder,
|
||||
boolean,
|
||||
index,
|
||||
integer,
|
||||
jsonb,
|
||||
numeric,
|
||||
pgEnum,
|
||||
PgNumericBuilder,
|
||||
PgVarcharBuilder,
|
||||
text,
|
||||
timestamp,
|
||||
varchar,
|
||||
} from 'drizzle-orm/pg-core'
|
||||
import type { Field, TabAsField } from 'payload/types'
|
||||
import { fieldAffectsData, optionIsObject } from 'payload/types'
|
||||
import { InvalidConfiguration } from 'payload/errors'
|
||||
import { fieldAffectsData, optionIsObject } from 'payload/types'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { GenericColumns, PostgresAdapter } from '../types'
|
||||
@@ -31,6 +32,7 @@ import { validateExistingBlockIsIdentical } from './validateExistingBlockIsIdent
|
||||
|
||||
type Args = {
|
||||
adapter: PostgresAdapter
|
||||
buildNumbers: boolean
|
||||
buildRelationships: boolean
|
||||
columnPrefix?: string
|
||||
columns: Record<string, PgColumnBuilder>
|
||||
@@ -60,6 +62,7 @@ type Result = {
|
||||
|
||||
export const traverseFields = ({
|
||||
adapter,
|
||||
buildNumbers,
|
||||
buildRelationships,
|
||||
columnPrefix,
|
||||
columns,
|
||||
@@ -283,19 +286,25 @@ export const traverseFields = ({
|
||||
baseExtraConfig._localeIdx = (cols) => index('_locale_idx').on(cols._locale)
|
||||
}
|
||||
|
||||
const { relationsToBuild: subRelationsToBuild } = buildTable({
|
||||
adapter,
|
||||
baseColumns,
|
||||
baseExtraConfig,
|
||||
disableNotNull: disableNotNullFromHere,
|
||||
disableUnique,
|
||||
fields: disableUnique ? idToUUID(field.fields) : field.fields,
|
||||
rootRelationsToBuild,
|
||||
rootRelationships: relationships,
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
tableName: arrayTableName,
|
||||
})
|
||||
const { hasManyNumberField: subHasManyNumberField, relationsToBuild: subRelationsToBuild } =
|
||||
buildTable({
|
||||
adapter,
|
||||
baseColumns,
|
||||
baseExtraConfig,
|
||||
disableNotNull: disableNotNullFromHere,
|
||||
disableUnique,
|
||||
fields: disableUnique ? idToUUID(field.fields) : field.fields,
|
||||
rootRelationsToBuild,
|
||||
rootRelationships: relationships,
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
tableName: arrayTableName,
|
||||
})
|
||||
|
||||
if (subHasManyNumberField) {
|
||||
if (!hasManyNumberField || subHasManyNumberField === 'index')
|
||||
hasManyNumberField = subHasManyNumberField
|
||||
}
|
||||
|
||||
relationsToBuild.set(fieldName, arrayTableName)
|
||||
|
||||
@@ -351,7 +360,10 @@ export const traverseFields = ({
|
||||
baseExtraConfig._localeIdx = (cols) => index('locale_idx').on(cols._locale)
|
||||
}
|
||||
|
||||
const { relationsToBuild: subRelationsToBuild } = buildTable({
|
||||
const {
|
||||
hasManyNumberField: subHasManyNumberField,
|
||||
relationsToBuild: subRelationsToBuild,
|
||||
} = buildTable({
|
||||
adapter,
|
||||
baseColumns,
|
||||
baseExtraConfig,
|
||||
@@ -365,6 +377,11 @@ export const traverseFields = ({
|
||||
tableName: blockTableName,
|
||||
})
|
||||
|
||||
if (subHasManyNumberField) {
|
||||
if (!hasManyNumberField || subHasManyNumberField === 'index')
|
||||
hasManyNumberField = subHasManyNumberField
|
||||
}
|
||||
|
||||
const blockTableRelations = relations(
|
||||
adapter.tables[blockTableName],
|
||||
({ many, one }) => {
|
||||
@@ -413,6 +430,7 @@ export const traverseFields = ({
|
||||
hasManyNumberField: groupHasManyNumberField,
|
||||
} = traverseFields({
|
||||
adapter,
|
||||
buildNumbers,
|
||||
buildRelationships,
|
||||
columnPrefix,
|
||||
columns,
|
||||
@@ -449,6 +467,7 @@ export const traverseFields = ({
|
||||
hasManyNumberField: groupHasManyNumberField,
|
||||
} = traverseFields({
|
||||
adapter,
|
||||
buildNumbers,
|
||||
buildRelationships,
|
||||
columnPrefix: `${columnName}_`,
|
||||
columns,
|
||||
@@ -486,6 +505,7 @@ export const traverseFields = ({
|
||||
hasManyNumberField: tabHasManyNumberField,
|
||||
} = traverseFields({
|
||||
adapter,
|
||||
buildNumbers,
|
||||
buildRelationships,
|
||||
columnPrefix,
|
||||
columns,
|
||||
@@ -524,6 +544,7 @@ export const traverseFields = ({
|
||||
hasManyNumberField: rowHasManyNumberField,
|
||||
} = traverseFields({
|
||||
adapter,
|
||||
buildNumbers,
|
||||
buildRelationships,
|
||||
columnPrefix,
|
||||
columns,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { CommitTransaction } from 'payload/database'
|
||||
|
||||
export const commitTransaction: CommitTransaction = async function commitTransaction(id) {
|
||||
// if the session was deleted it has already been aborted
|
||||
if (!this.sessions[id]) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,12 +3,15 @@ import type { RollbackTransaction } from 'payload/database'
|
||||
export const rollbackTransaction: RollbackTransaction = async function rollbackTransaction(
|
||||
id = '',
|
||||
) {
|
||||
// if multiple operations are using the same transaction, the first will flow through and delete the session.
|
||||
// subsequent calls should be ignored.
|
||||
if (!this.sessions[id]) {
|
||||
this.payload.logger.warn('rollbackTransaction called when no transaction exists')
|
||||
return
|
||||
}
|
||||
|
||||
// end the session promise in failure by calling reject
|
||||
await this.sessions[id].reject()
|
||||
|
||||
// delete the session causing any other operations with the same transaction to fail
|
||||
delete this.sessions[id]
|
||||
}
|
||||
|
||||
@@ -3,16 +3,18 @@ import { isArrayOfRows } from '../../utilities/isArrayOfRows'
|
||||
|
||||
type Args = {
|
||||
data: unknown
|
||||
id?: unknown
|
||||
locale?: string
|
||||
}
|
||||
|
||||
export const transformSelects = ({ data, locale }: Args) => {
|
||||
export const transformSelects = ({ id, data, locale }: Args) => {
|
||||
const newRows: Record<string, unknown>[] = []
|
||||
|
||||
if (isArrayOfRows(data)) {
|
||||
data.forEach((value, i) => {
|
||||
const newRow: Record<string, unknown> = {
|
||||
order: i + 1,
|
||||
parent: id,
|
||||
value,
|
||||
}
|
||||
|
||||
|
||||
@@ -422,6 +422,7 @@ export const traverseFields = ({
|
||||
Object.entries(data[field.name]).forEach(([localeKey, localeData]) => {
|
||||
if (Array.isArray(localeData)) {
|
||||
const newRows = transformSelects({
|
||||
id: data._uuid || data.id,
|
||||
data: localeData,
|
||||
locale: localeKey,
|
||||
})
|
||||
@@ -432,6 +433,7 @@ export const traverseFields = ({
|
||||
}
|
||||
} else if (Array.isArray(data[field.name])) {
|
||||
const newRows = transformSelects({
|
||||
id: data._uuid || data.id,
|
||||
data: data[field.name],
|
||||
})
|
||||
|
||||
|
||||
@@ -102,7 +102,9 @@ export const upsertRow = async <T extends TypeWithID>({
|
||||
if (Object.keys(rowToInsert.selects).length > 0) {
|
||||
Object.entries(rowToInsert.selects).forEach(([selectTableName, selectRows]) => {
|
||||
selectRows.forEach((row) => {
|
||||
row.parent = insertedRow.id
|
||||
if (typeof row.parent === 'undefined') {
|
||||
row.parent = insertedRow.id
|
||||
}
|
||||
if (!selectsToInsert[selectTableName]) selectsToInsert[selectTableName] = []
|
||||
selectsToInsert[selectTableName].push(row)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "2.0.15",
|
||||
"version": "2.1.0",
|
||||
"description": "Node, React and MongoDB Headless CMS and Application Framework",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -1,28 +1,33 @@
|
||||
import React from 'react'
|
||||
import { Chevron } from '../../..'
|
||||
import { useLocale } from '../../../utilities/Locale'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { Chevron } from '../../..'
|
||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||
import { useLocale } from '../../../utilities/Locale'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'localizer-button'
|
||||
|
||||
export const LocalizerLabel: React.FC<{
|
||||
className?: string
|
||||
ariaLabel?: string
|
||||
className?: string
|
||||
}> = (props) => {
|
||||
const { className, ariaLabel } = props
|
||||
const { ariaLabel, className } = props
|
||||
const locale = useLocale()
|
||||
const { t } = useTranslation('general')
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[baseClass, className].filter(Boolean).join(' ')}
|
||||
aria-label={ariaLabel || t('locale')}
|
||||
className={[baseClass, className].filter(Boolean).join(' ')}
|
||||
>
|
||||
<div className={`${baseClass}__label`}>{`${t('locale')}:`}</div>
|
||||
|
||||
<span className={`${baseClass}__current-label`}>{`${locale.label}`}</span>
|
||||
<span className={`${baseClass}__current-label`}>{`${getTranslation(
|
||||
locale.label,
|
||||
i18n,
|
||||
)}`}</span>
|
||||
|
||||
<Chevron className={`${baseClass}__chevron`} />
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import qs from 'qs'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { getTranslation } from '../../../../utilities/getTranslation'
|
||||
import { useConfig } from '../../utilities/Config'
|
||||
import { useLocale } from '../../utilities/Locale'
|
||||
import { useSearchParams } from '../../utilities/SearchParams'
|
||||
@@ -18,9 +20,12 @@ const Localizer: React.FC<{
|
||||
const config = useConfig()
|
||||
const { localization } = config
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
const locale = useLocale()
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
const localeLabel = getTranslation(locale.label, i18n)
|
||||
|
||||
if (localization) {
|
||||
const { locales } = localization
|
||||
|
||||
@@ -44,8 +49,8 @@ const Localizer: React.FC<{
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{locale.label}
|
||||
{locale.label !== locale.code && ` (${locale.code})`}
|
||||
{localeLabel}
|
||||
{localeLabel !== locale.code && ` (${locale.code})`}
|
||||
</PopupList.Button>
|
||||
) : null}
|
||||
|
||||
@@ -57,11 +62,12 @@ const Localizer: React.FC<{
|
||||
locale: localeOption.code,
|
||||
}
|
||||
const search = qs.stringify(newParams)
|
||||
const localeOptionLabel = getTranslation(localeOption.label, i18n)
|
||||
|
||||
return (
|
||||
<PopupList.Button key={localeOption.code} onClick={close} to={{ search }}>
|
||||
{localeOption.label}
|
||||
{localeOption.label !== localeOption.code && ` (${localeOption.code})`}
|
||||
{localeOptionLabel}
|
||||
{localeOptionLabel !== localeOption.code && ` (${localeOption.code})`}
|
||||
</PopupList.Button>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -6,9 +6,9 @@ import DatePicker from '../../../DatePicker'
|
||||
|
||||
const baseClass = 'condition-value-date'
|
||||
|
||||
const DateField: React.FC<Props> = ({ onChange, value }) => (
|
||||
const DateField: React.FC<Props> = ({ disabled, onChange, value }) => (
|
||||
<div className={baseClass}>
|
||||
<DatePicker onChange={onChange} value={value} />
|
||||
<DatePicker onChange={onChange} readOnly={disabled} value={value} />
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export type Props = {
|
||||
disabled?: boolean
|
||||
onChange: () => void
|
||||
value: Date
|
||||
}
|
||||
|
||||
@@ -7,11 +7,12 @@ import './index.scss'
|
||||
|
||||
const baseClass = 'condition-value-number'
|
||||
|
||||
const NumberField: React.FC<Props> = ({ onChange, value }) => {
|
||||
const NumberField: React.FC<Props> = ({ disabled, onChange, value }) => {
|
||||
const { t } = useTranslation('general')
|
||||
return (
|
||||
<input
|
||||
className={baseClass}
|
||||
disabled={disabled}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={t('enterAValue')}
|
||||
type="number"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export type Props = {
|
||||
disabled?: boolean
|
||||
onChange: (e: string) => void
|
||||
value: string
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ const baseClass = 'condition-value-relationship'
|
||||
const maxResultsPerRequest = 10
|
||||
|
||||
const RelationshipField: React.FC<Props> = (props) => {
|
||||
const { admin: { isSortable } = {}, hasMany, onChange, relationTo, value } = props
|
||||
const { admin: { isSortable } = {}, disabled, hasMany, onChange, relationTo, value } = props
|
||||
|
||||
const {
|
||||
collections,
|
||||
@@ -261,6 +261,7 @@ const RelationshipField: React.FC<Props> = (props) => {
|
||||
<div className={classes}>
|
||||
{!errorLoading && (
|
||||
<ReactSelect
|
||||
disabled={disabled}
|
||||
isMulti={hasMany}
|
||||
isSortable={isSortable}
|
||||
onChange={(selected) => {
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { PaginatedDocs } from '../../../../../../database/types'
|
||||
import type { RelationshipField } from '../../../../../../fields/config/types'
|
||||
|
||||
export type Props = {
|
||||
disabled?: boolean
|
||||
onChange: (val: unknown) => void
|
||||
value: unknown
|
||||
} & RelationshipField
|
||||
|
||||
@@ -20,6 +20,7 @@ const formatOptions = (options: Option[]): OptionObject[] =>
|
||||
})
|
||||
|
||||
export const Select: React.FC<Props> = ({
|
||||
disabled,
|
||||
onChange,
|
||||
operator,
|
||||
options: optionsFromProps,
|
||||
@@ -79,6 +80,7 @@ export const Select: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<ReactSelect
|
||||
disabled={disabled}
|
||||
isMulti={isMulti}
|
||||
onChange={onSelect}
|
||||
options={options.map((option) => ({ ...option, label: getTranslation(option.label, i18n) }))}
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Option } from '../../../../../../fields/config/types'
|
||||
import type { Operator } from '../../../../../../types'
|
||||
|
||||
export type Props = {
|
||||
disabled?: boolean
|
||||
onChange: (val: string) => void
|
||||
operator: Operator
|
||||
options: Option[]
|
||||
|
||||
@@ -7,11 +7,12 @@ import './index.scss'
|
||||
|
||||
const baseClass = 'condition-value-text'
|
||||
|
||||
const Text: React.FC<Props> = ({ onChange, value }) => {
|
||||
const Text: React.FC<Props> = ({ disabled, onChange, value }) => {
|
||||
const { t } = useTranslation('general')
|
||||
return (
|
||||
<input
|
||||
className={baseClass}
|
||||
disabled={disabled}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={t('enterAValue')}
|
||||
type="text"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export type Props = {
|
||||
disabled?: boolean
|
||||
onChange: (val: string) => void
|
||||
value: string
|
||||
}
|
||||
|
||||
@@ -26,25 +26,29 @@ const baseClass = 'condition'
|
||||
|
||||
const Condition: React.FC<Props> = (props) => {
|
||||
const { andIndex, dispatch, fields, orIndex, value } = props
|
||||
const fieldValue = Object.keys(value)[0]
|
||||
const operatorAndValue = value?.[fieldValue] ? Object.entries(value[fieldValue])[0] : undefined
|
||||
|
||||
const operatorValue = operatorAndValue?.[0]
|
||||
const queryValue = operatorAndValue?.[1]
|
||||
|
||||
const fieldName = Object.keys(value)[0]
|
||||
const [activeField, setActiveField] = useState<FieldCondition>(() =>
|
||||
fields.find((field) => fieldValue === field.value),
|
||||
fields.find((field) => fieldName === field.value),
|
||||
)
|
||||
|
||||
const operatorAndValue = value?.[fieldName] ? Object.entries(value[fieldName])[0] : undefined
|
||||
const queryValue = operatorAndValue?.[1]
|
||||
const operatorValue = operatorAndValue?.[0]
|
||||
|
||||
const [internalValue, setInternalValue] = useState(queryValue)
|
||||
const [internalOperatorField, setInternalOperatorField] = useState(operatorValue)
|
||||
|
||||
const debouncedValue = useDebounce(internalValue, 300)
|
||||
|
||||
useEffect(() => {
|
||||
const newActiveField = fields.find((field) => fieldValue === field.value)
|
||||
const newActiveField = fields.find(({ value: name }) => name === fieldName)
|
||||
|
||||
if (newActiveField) {
|
||||
if (newActiveField && newActiveField !== activeField) {
|
||||
setActiveField(newActiveField)
|
||||
setInternalOperatorField(null)
|
||||
setInternalValue('')
|
||||
}
|
||||
}, [fieldValue, fields])
|
||||
}, [fieldName, fields, activeField])
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
@@ -73,21 +77,23 @@ const Condition: React.FC<Props> = (props) => {
|
||||
<div className={`${baseClass}__inputs`}>
|
||||
<div className={`${baseClass}__field`}>
|
||||
<ReactSelect
|
||||
onChange={(field) =>
|
||||
isClearable={false}
|
||||
onChange={(field) => {
|
||||
dispatch({
|
||||
andIndex,
|
||||
field: field?.value || undefined,
|
||||
orIndex,
|
||||
andIndex: andIndex,
|
||||
field: field?.value,
|
||||
orIndex: orIndex,
|
||||
type: 'update',
|
||||
})
|
||||
}
|
||||
}}
|
||||
options={fields}
|
||||
value={fields.find((field) => fieldValue === field.value)}
|
||||
value={fields.find((field) => fieldName === field.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__operator`}>
|
||||
<ReactSelect
|
||||
disabled={!fieldValue}
|
||||
disabled={!fieldName}
|
||||
isClearable={false}
|
||||
onChange={(operator) => {
|
||||
dispatch({
|
||||
andIndex,
|
||||
@@ -95,9 +101,14 @@ const Condition: React.FC<Props> = (props) => {
|
||||
orIndex,
|
||||
type: 'update',
|
||||
})
|
||||
setInternalOperatorField(operator.value)
|
||||
}}
|
||||
options={activeField.operators}
|
||||
value={activeField.operators.find((operator) => operatorValue === operator.value)}
|
||||
value={
|
||||
activeField.operators.find(
|
||||
(operator) => internalOperatorField === operator.value,
|
||||
) || null
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__value`}>
|
||||
@@ -106,6 +117,7 @@ const Condition: React.FC<Props> = (props) => {
|
||||
DefaultComponent={ValueComponent}
|
||||
componentProps={{
|
||||
...activeField?.props,
|
||||
disabled: !operatorValue,
|
||||
onChange: setInternalValue,
|
||||
operator: operatorValue,
|
||||
options: valueOptions,
|
||||
|
||||
@@ -59,17 +59,17 @@ const reducer = (state: Where[], action: Action): Where[] => {
|
||||
|
||||
if (field) {
|
||||
newState[orIndex].and[andIndex] = {
|
||||
[field]: {
|
||||
[Object.keys(existingCondition)[0]]: Object.values(existingCondition)[0],
|
||||
},
|
||||
[field]: operator ? { [operator]: value } : {},
|
||||
}
|
||||
}
|
||||
|
||||
if (value !== undefined) {
|
||||
newState[orIndex].and[andIndex] = {
|
||||
[existingFieldName]: {
|
||||
[Object.keys(existingCondition)[0]]: value,
|
||||
},
|
||||
[existingFieldName]: Object.keys(existingCondition)[0]
|
||||
? {
|
||||
[Object.keys(existingCondition)[0]]: value,
|
||||
}
|
||||
: {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,9 +281,9 @@ export const addFieldStatePromise = async ({
|
||||
return {
|
||||
relationTo: relationship.relationTo,
|
||||
value:
|
||||
typeof relationship.value === 'string'
|
||||
? relationship.value
|
||||
: relationship.value?.id,
|
||||
relationship.value && typeof relationship.value === 'object'
|
||||
? relationship.value?.id
|
||||
: relationship.value,
|
||||
}
|
||||
}
|
||||
if (typeof relationship === 'object' && relationship !== null) {
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import React from 'react'
|
||||
|
||||
import type { Props as LabelProps } from '../../Label/types'
|
||||
|
||||
import Check from '../../../icons/Check'
|
||||
import Line from '../../../icons/Line'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'checkbox-input'
|
||||
|
||||
type CheckboxInputProps = {
|
||||
AfterInput?: React.ReactElement<any>[]
|
||||
BeforeInput?: React.ReactElement<any>[]
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
'aria-label'?: string
|
||||
checked?: boolean
|
||||
className?: string
|
||||
@@ -25,6 +30,9 @@ export const CheckboxInput: React.FC<CheckboxInputProps> = (props) => {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
AfterInput,
|
||||
BeforeInput,
|
||||
Label,
|
||||
'aria-label': ariaLabel,
|
||||
checked,
|
||||
className,
|
||||
@@ -36,6 +44,8 @@ export const CheckboxInput: React.FC<CheckboxInputProps> = (props) => {
|
||||
required,
|
||||
} = props
|
||||
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
@@ -48,6 +58,7 @@ export const CheckboxInput: React.FC<CheckboxInputProps> = (props) => {
|
||||
.join(' ')}
|
||||
>
|
||||
<div className={`${baseClass}__input`}>
|
||||
{BeforeInput}
|
||||
<input
|
||||
aria-label={ariaLabel}
|
||||
defaultChecked={Boolean(checked)}
|
||||
@@ -58,12 +69,13 @@ export const CheckboxInput: React.FC<CheckboxInputProps> = (props) => {
|
||||
ref={inputRef}
|
||||
type="checkbox"
|
||||
/>
|
||||
{AfterInput}
|
||||
<span className={`${baseClass}__icon ${!partialChecked ? 'check' : 'partial'}`}>
|
||||
{!partialChecked && <Check />}
|
||||
{partialChecked && <Line />}
|
||||
</span>
|
||||
</div>
|
||||
{label && <Label htmlFor={id} label={label} required={required} />}
|
||||
{label && <LabelComp htmlFor={id} label={label} required={required} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { Props } from './types'
|
||||
|
||||
import { checkbox } from '../../../../../fields/validations'
|
||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import useField from '../../useField'
|
||||
import withCondition from '../../withCondition'
|
||||
@@ -18,7 +18,15 @@ const baseClass = 'checkbox'
|
||||
const Checkbox: React.FC<Props> = (props) => {
|
||||
const {
|
||||
name,
|
||||
admin: { className, condition, description, readOnly, style, width } = {},
|
||||
admin: {
|
||||
className,
|
||||
condition,
|
||||
description,
|
||||
readOnly,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label, BeforeInput, AfterInput } = {},
|
||||
} = {},
|
||||
disableFormData,
|
||||
label,
|
||||
onChange,
|
||||
@@ -27,6 +35,8 @@ const Checkbox: React.FC<Props> = (props) => {
|
||||
validate = checkbox,
|
||||
} = props
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const path = pathFromProps || name
|
||||
@@ -72,7 +82,7 @@ const Checkbox: React.FC<Props> = (props) => {
|
||||
}}
|
||||
>
|
||||
<div className={`${baseClass}__error-wrap`}>
|
||||
<Error alignCaret="left" message={errorMessage} showError={showError} />
|
||||
<ErrorComp alignCaret="left" message={errorMessage} showError={showError} />
|
||||
</div>
|
||||
<CheckboxInput
|
||||
checked={Boolean(value)}
|
||||
@@ -81,6 +91,9 @@ const Checkbox: React.FC<Props> = (props) => {
|
||||
name={path}
|
||||
onToggle={onToggle}
|
||||
readOnly={readOnly}
|
||||
Label={Label}
|
||||
BeforeInput={BeforeInput}
|
||||
AfterInput={AfterInput}
|
||||
required={required}
|
||||
/>
|
||||
<FieldDescription description={description} value={value} />
|
||||
|
||||
@@ -4,9 +4,9 @@ import type { Props } from './types'
|
||||
|
||||
import { code } from '../../../../../fields/validations'
|
||||
import { CodeEditor } from '../../../elements/CodeEditor'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import useField from '../../useField'
|
||||
import withCondition from '../../withCondition'
|
||||
import './index.scss'
|
||||
@@ -31,6 +31,7 @@ const Code: React.FC<Props> = (props) => {
|
||||
readOnly,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label } = {},
|
||||
} = {},
|
||||
label,
|
||||
path: pathFromProps,
|
||||
@@ -38,6 +39,9 @@ const Code: React.FC<Props> = (props) => {
|
||||
validate = code,
|
||||
} = props
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
const path = pathFromProps || name
|
||||
|
||||
const memoizedValidate = useCallback(
|
||||
@@ -69,8 +73,8 @@ const Code: React.FC<Props> = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<Label htmlFor={`field-${path}`} label={label} required={required} />
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
<LabelComp htmlFor={`field-${path}`} label={label} required={required} />
|
||||
<CodeEditor
|
||||
defaultLanguage={prismToMonacoLanguageMap[language] || language}
|
||||
onChange={readOnly ? () => null : (val) => setValue(val)}
|
||||
|
||||
@@ -6,9 +6,9 @@ import type { Description } from '../../FieldDescription/types'
|
||||
|
||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||
import DatePicker from '../../../elements/DatePicker'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import './index.scss'
|
||||
|
||||
@@ -16,6 +16,12 @@ const baseClass = 'date-time-field'
|
||||
|
||||
export type DateTimeInputProps = Omit<DateField, 'admin' | 'name' | 'type'> & {
|
||||
className?: string
|
||||
components: {
|
||||
AfterInput?: React.ReactElement<any>[]
|
||||
BeforeInput?: React.ReactElement<any>[]
|
||||
Error?: React.ComponentType<any>
|
||||
Label?: React.ComponentType<any>
|
||||
}
|
||||
datePickerProps?: DateField['admin']['date']
|
||||
description?: Description
|
||||
errorMessage?: string
|
||||
@@ -33,6 +39,7 @@ export type DateTimeInputProps = Omit<DateField, 'admin' | 'name' | 'type'> & {
|
||||
export const DateTimeInput: React.FC<DateTimeInputProps> = (props) => {
|
||||
const {
|
||||
className,
|
||||
components: { AfterInput, BeforeInput, Error, Label } = {},
|
||||
datePickerProps,
|
||||
description,
|
||||
errorMessage,
|
||||
@@ -48,6 +55,9 @@ export const DateTimeInput: React.FC<DateTimeInputProps> = (props) => {
|
||||
width,
|
||||
} = props
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
return (
|
||||
@@ -67,10 +77,11 @@ export const DateTimeInput: React.FC<DateTimeInputProps> = (props) => {
|
||||
}}
|
||||
>
|
||||
<div className={`${baseClass}__error-wrap`}>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
</div>
|
||||
<Label htmlFor={path} label={label} required={required} />
|
||||
<LabelComp htmlFor={path} label={label} required={required} />
|
||||
<div className={`${baseClass}__input-wrapper`} id={`field-${path.replace(/\./g, '__')}`}>
|
||||
{BeforeInput}
|
||||
<DatePicker
|
||||
{...datePickerProps}
|
||||
onChange={onChange}
|
||||
@@ -78,6 +89,7 @@ export const DateTimeInput: React.FC<DateTimeInputProps> = (props) => {
|
||||
readOnly={readOnly}
|
||||
value={value}
|
||||
/>
|
||||
{AfterInput}
|
||||
</div>
|
||||
<FieldDescription description={description} value={value} />
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,17 @@ import './index.scss'
|
||||
const DateTime: React.FC<Props> = (props) => {
|
||||
const {
|
||||
name,
|
||||
admin: { className, condition, date, description, placeholder, readOnly, style, width } = {},
|
||||
admin: {
|
||||
className,
|
||||
components,
|
||||
condition,
|
||||
date,
|
||||
description,
|
||||
placeholder,
|
||||
readOnly,
|
||||
style,
|
||||
width,
|
||||
} = {},
|
||||
label,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
@@ -36,6 +46,7 @@ const DateTime: React.FC<Props> = (props) => {
|
||||
return (
|
||||
<DateTimeInput
|
||||
className={className}
|
||||
components={components}
|
||||
datePickerProps={date}
|
||||
description={description}
|
||||
errorMessage={errorMessage}
|
||||
|
||||
@@ -5,9 +5,9 @@ import type { Props } from './types'
|
||||
|
||||
import { email } from '../../../../../fields/validations'
|
||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import useField from '../../useField'
|
||||
import withCondition from '../../withCondition'
|
||||
import './index.scss'
|
||||
@@ -25,6 +25,7 @@ const Email: React.FC<Props> = (props) => {
|
||||
readOnly,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label, BeforeInput, AfterInput } = {},
|
||||
} = {},
|
||||
label,
|
||||
path: pathFromProps,
|
||||
@@ -51,6 +52,9 @@ const Email: React.FC<Props> = (props) => {
|
||||
|
||||
const { errorMessage, setValue, showError, value } = fieldType
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[fieldBaseClass, 'email', className, showError && 'error', readOnly && 'read-only']
|
||||
@@ -61,18 +65,22 @@ const Email: React.FC<Props> = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<Label htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<input
|
||||
autoComplete={autoComplete}
|
||||
disabled={Boolean(readOnly)}
|
||||
id={`field-${path.replace(/\./g, '__')}`}
|
||||
name={path}
|
||||
onChange={setValue}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
type="email"
|
||||
value={(value as string) || ''}
|
||||
/>
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<div className="input-wrapper">
|
||||
{BeforeInput}
|
||||
<input
|
||||
autoComplete={autoComplete}
|
||||
disabled={Boolean(readOnly)}
|
||||
id={`field-${path.replace(/\./g, '__')}`}
|
||||
name={path}
|
||||
onChange={setValue}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
type="email"
|
||||
value={(value as string) || ''}
|
||||
/>
|
||||
{AfterInput}
|
||||
</div>
|
||||
<FieldDescription description={description} value={value} />
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -4,9 +4,9 @@ import type { Props } from './types'
|
||||
|
||||
import { json } from '../../../../../fields/validations'
|
||||
import { CodeEditor } from '../../../elements/CodeEditor'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import useField from '../../useField'
|
||||
import withCondition from '../../withCondition'
|
||||
import './index.scss'
|
||||
@@ -17,13 +17,25 @@ const baseClass = 'json-field'
|
||||
const JSONField: React.FC<Props> = (props) => {
|
||||
const {
|
||||
name,
|
||||
admin: { className, condition, description, editorOptions, readOnly, style, width } = {},
|
||||
admin: {
|
||||
className,
|
||||
condition,
|
||||
description,
|
||||
editorOptions,
|
||||
readOnly,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label } = {},
|
||||
} = {},
|
||||
label,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
validate = json,
|
||||
} = props
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
const path = pathFromProps || name
|
||||
const [stringValue, setStringValue] = useState<string>()
|
||||
const [jsonError, setJsonError] = useState<string>()
|
||||
@@ -76,8 +88,8 @@ const JSONField: React.FC<Props> = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<Label htmlFor={`field-${path}`} label={label} required={required} />
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
<LabelComp htmlFor={`field-${path}`} label={label} required={required} />
|
||||
<CodeEditor
|
||||
defaultLanguage="json"
|
||||
onChange={handleChange}
|
||||
|
||||
@@ -8,9 +8,9 @@ import { number } from '../../../../../fields/validations'
|
||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||
import { isNumber } from '../../../../../utilities/isNumber'
|
||||
import ReactSelect from '../../../elements/ReactSelect'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import useField from '../../useField'
|
||||
import withCondition from '../../withCondition'
|
||||
import './index.scss'
|
||||
@@ -19,7 +19,17 @@ import { fieldBaseClass } from '../shared'
|
||||
const NumberField: React.FC<Props> = (props) => {
|
||||
const {
|
||||
name,
|
||||
admin: { className, condition, description, placeholder, readOnly, step, style, width } = {},
|
||||
admin: {
|
||||
className,
|
||||
condition,
|
||||
description,
|
||||
placeholder,
|
||||
readOnly,
|
||||
step,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label, BeforeInput, AfterInput } = {},
|
||||
} = {},
|
||||
hasMany,
|
||||
label,
|
||||
max,
|
||||
@@ -31,6 +41,9 @@ const NumberField: React.FC<Props> = (props) => {
|
||||
validate = number,
|
||||
} = props
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
const { i18n, t } = useTranslation()
|
||||
|
||||
const path = pathFromProps || name
|
||||
@@ -118,8 +131,8 @@ const NumberField: React.FC<Props> = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<Label htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
{hasMany ? (
|
||||
<ReactSelect
|
||||
className={`field-${path.replace(/\./g, '__')}`}
|
||||
@@ -148,21 +161,25 @@ const NumberField: React.FC<Props> = (props) => {
|
||||
value={valueToRender as Option[]}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
disabled={readOnly}
|
||||
id={`field-${path.replace(/\./g, '__')}`}
|
||||
name={path}
|
||||
onChange={handleChange}
|
||||
onWheel={(e) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
e.target.blur()
|
||||
}}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
step={step}
|
||||
type="number"
|
||||
value={typeof value === 'number' ? value : ''}
|
||||
/>
|
||||
<div className="input-wrapper">
|
||||
{BeforeInput}
|
||||
<input
|
||||
disabled={readOnly}
|
||||
id={`field-${path.replace(/\./g, '__')}`}
|
||||
name={path}
|
||||
onChange={handleChange}
|
||||
onWheel={(e) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
e.target.blur()
|
||||
}}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
step={step}
|
||||
type="number"
|
||||
value={typeof value === 'number' ? value : ''}
|
||||
/>
|
||||
{AfterInput}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FieldDescription description={description} value={value} />
|
||||
|
||||
@@ -5,9 +5,9 @@ import type { Props } from './types'
|
||||
|
||||
import { point } from '../../../../../fields/validations'
|
||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import useField from '../../useField'
|
||||
import withCondition from '../../withCondition'
|
||||
import './index.scss'
|
||||
@@ -18,13 +18,26 @@ const baseClass = 'point'
|
||||
const PointField: React.FC<Props> = (props) => {
|
||||
const {
|
||||
name,
|
||||
admin: { className, condition, description, placeholder, readOnly, step, style, width } = {},
|
||||
admin: {
|
||||
className,
|
||||
condition,
|
||||
description,
|
||||
placeholder,
|
||||
readOnly,
|
||||
step,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label, BeforeInput, AfterInput } = {},
|
||||
} = {},
|
||||
label,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
validate = point,
|
||||
} = props
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
const path = pathFromProps || name
|
||||
|
||||
const { i18n, t } = useTranslation('fields')
|
||||
@@ -76,41 +89,49 @@ const PointField: React.FC<Props> = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
<ul className={`${baseClass}__wrap`}>
|
||||
<li>
|
||||
<Label
|
||||
<LabelComp
|
||||
htmlFor={`field-longitude-${path.replace(/\./g, '__')}`}
|
||||
label={`${getTranslation(label || name, i18n)} - ${t('longitude')}`}
|
||||
required={required}
|
||||
/>
|
||||
<input
|
||||
disabled={readOnly}
|
||||
id={`field-longitude-${path.replace(/\./g, '__')}`}
|
||||
name={`${path}.longitude`}
|
||||
onChange={(e) => handleChange(e, 0)}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
step={step}
|
||||
type="number"
|
||||
value={value && typeof value[0] === 'number' ? value[0] : ''}
|
||||
/>
|
||||
<div className="input-wrapper">
|
||||
{BeforeInput}
|
||||
<input
|
||||
disabled={readOnly}
|
||||
id={`field-longitude-${path.replace(/\./g, '__')}`}
|
||||
name={`${path}.longitude`}
|
||||
onChange={(e) => handleChange(e, 0)}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
step={step}
|
||||
type="number"
|
||||
value={value && typeof value[0] === 'number' ? value[0] : ''}
|
||||
/>
|
||||
{AfterInput}
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<Label
|
||||
<LabelComp
|
||||
htmlFor={`field-latitude-${path.replace(/\./g, '__')}`}
|
||||
label={`${getTranslation(label || name, i18n)} - ${t('latitude')}`}
|
||||
required={required}
|
||||
/>
|
||||
<input
|
||||
disabled={readOnly}
|
||||
id={`field-latitude-${path.replace(/\./g, '__')}`}
|
||||
name={`${path}.latitude`}
|
||||
onChange={(e) => handleChange(e, 1)}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
step={step}
|
||||
type="number"
|
||||
value={value && typeof value[1] === 'number' ? value[1] : ''}
|
||||
/>
|
||||
<div className="input-wrapper">
|
||||
{BeforeInput}
|
||||
<input
|
||||
disabled={readOnly}
|
||||
id={`field-latitude-${path.replace(/\./g, '__')}`}
|
||||
name={`${path}.latitude`}
|
||||
onChange={(e) => handleChange(e, 1)}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
step={step}
|
||||
type="number"
|
||||
value={value && typeof value[1] === 'number' ? value[1] : ''}
|
||||
/>
|
||||
{AfterInput}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<FieldDescription description={description} value={value} />
|
||||
|
||||
@@ -5,9 +5,9 @@ import type { Description } from '../../FieldDescription/types'
|
||||
import type { OnChange } from './types'
|
||||
|
||||
import { optionIsObject } from '../../../../../fields/config/types'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import RadioInput from './RadioInput'
|
||||
import './index.scss'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
@@ -28,6 +28,8 @@ export type RadioGroupInputProps = Omit<RadioField, 'type'> & {
|
||||
style?: React.CSSProperties
|
||||
value?: string
|
||||
width?: string
|
||||
Error?: React.ComponentType<any>
|
||||
Label?: React.ComponentType<any>
|
||||
}
|
||||
|
||||
const RadioGroupInput: React.FC<RadioGroupInputProps> = (props) => {
|
||||
@@ -47,8 +49,13 @@ const RadioGroupInput: React.FC<RadioGroupInputProps> = (props) => {
|
||||
style,
|
||||
value,
|
||||
width,
|
||||
Error,
|
||||
Label,
|
||||
} = props
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
const path = pathFromProps || name
|
||||
|
||||
return (
|
||||
@@ -69,9 +76,9 @@ const RadioGroupInput: React.FC<RadioGroupInputProps> = (props) => {
|
||||
}}
|
||||
>
|
||||
<div className={`${baseClass}__error-wrap`}>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
</div>
|
||||
<Label htmlFor={`field-${path}`} label={label} required={required} />
|
||||
<LabelComp htmlFor={`field-${path}`} label={label} required={required} />
|
||||
<ul className={`${baseClass}--group`} id={`field-${path.replace(/\./g, '__')}`}>
|
||||
{options.map((option) => {
|
||||
let optionValue = ''
|
||||
|
||||
@@ -18,6 +18,7 @@ const RadioGroup: React.FC<Props> = (props) => {
|
||||
readOnly,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label } = {},
|
||||
} = {},
|
||||
label,
|
||||
options,
|
||||
@@ -57,6 +58,8 @@ const RadioGroup: React.FC<Props> = (props) => {
|
||||
style={style}
|
||||
value={value}
|
||||
width={width}
|
||||
Error={Error}
|
||||
Label={Label}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,12 +15,13 @@ import { useAuth } from '../../../utilities/Auth'
|
||||
import { useConfig } from '../../../utilities/Config'
|
||||
import { GetFilterOptions } from '../../../utilities/GetFilterOptions'
|
||||
import { useLocale } from '../../../utilities/Locale'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import { useFormProcessing } from '../../Form/context'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import useField from '../../useField'
|
||||
import withCondition from '../../withCondition'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import { AddNewRelation } from './AddNew'
|
||||
import { createRelationMap } from './createRelationMap'
|
||||
import { findOptionsByValue } from './findOptionsByValue'
|
||||
@@ -28,7 +29,6 @@ import './index.scss'
|
||||
import optionsReducer from './optionsReducer'
|
||||
import { MultiValueLabel } from './select-components/MultiValueLabel'
|
||||
import { SingleValue } from './select-components/SingleValue'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
|
||||
const maxResultsPerRequest = 10
|
||||
|
||||
@@ -46,6 +46,7 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
readOnly,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label } = {},
|
||||
} = {},
|
||||
filterOptions,
|
||||
hasMany,
|
||||
@@ -56,6 +57,9 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
validate = relationship,
|
||||
} = props
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
const config = useConfig()
|
||||
|
||||
const {
|
||||
@@ -391,6 +395,7 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
}, [])
|
||||
|
||||
const valueToRender = findOptionsByValue({ options, value })
|
||||
|
||||
if (!Array.isArray(valueToRender) && valueToRender?.value === 'null') valueToRender.value = null
|
||||
|
||||
return (
|
||||
@@ -411,8 +416,8 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<Label htmlFor={pathOrName} label={label} required={required} />
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
<LabelComp htmlFor={pathOrName} label={label} required={required} />
|
||||
<GetFilterOptions
|
||||
{...{
|
||||
filterOptions,
|
||||
|
||||
@@ -7,9 +7,9 @@ import type { Description } from '../../FieldDescription/types'
|
||||
|
||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||
import ReactSelect from '../../../elements/ReactSelect'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import './index.scss'
|
||||
|
||||
@@ -29,6 +29,8 @@ export type SelectInputProps = Omit<SelectField, 'options' | 'type' | 'value'> &
|
||||
style?: React.CSSProperties
|
||||
value?: string | string[]
|
||||
width?: string
|
||||
Error?: React.ComponentType<any>
|
||||
Label?: React.ComponentType<any>
|
||||
}
|
||||
|
||||
const SelectInput: React.FC<SelectInputProps> = (props) => {
|
||||
@@ -50,10 +52,15 @@ const SelectInput: React.FC<SelectInputProps> = (props) => {
|
||||
style,
|
||||
value,
|
||||
width,
|
||||
Error,
|
||||
Label,
|
||||
} = props
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
let valueToRender = defaultValue
|
||||
|
||||
if (hasMany && Array.isArray(value)) {
|
||||
@@ -89,8 +96,8 @@ const SelectInput: React.FC<SelectInputProps> = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<Label htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<ReactSelect
|
||||
disabled={readOnly}
|
||||
isClearable={isClearable}
|
||||
|
||||
@@ -32,6 +32,7 @@ const Select: React.FC<Props> = (props) => {
|
||||
readOnly,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label } = {},
|
||||
} = {},
|
||||
hasMany,
|
||||
label,
|
||||
@@ -103,6 +104,8 @@ const Select: React.FC<Props> = (props) => {
|
||||
style={style}
|
||||
value={value as string | string[]}
|
||||
width={width}
|
||||
Error={Error}
|
||||
Label={Label}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import type { TextField } from '../../../../../fields/config/types'
|
||||
import type { Description } from '../../FieldDescription/types'
|
||||
|
||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import './index.scss'
|
||||
|
||||
@@ -29,6 +29,10 @@ export type TextInputProps = Omit<TextField, 'type'> & {
|
||||
style?: React.CSSProperties
|
||||
value?: string
|
||||
width?: string
|
||||
Error?: React.ComponentType<any>
|
||||
Label?: React.ComponentType<any>
|
||||
BeforeInput?: React.ReactElement<any>[]
|
||||
AfterInput?: React.ReactElement<any>[]
|
||||
}
|
||||
|
||||
const TextInput: React.FC<TextInputProps> = (props) => {
|
||||
@@ -49,10 +53,17 @@ const TextInput: React.FC<TextInputProps> = (props) => {
|
||||
style,
|
||||
value,
|
||||
width,
|
||||
Error,
|
||||
Label,
|
||||
BeforeInput,
|
||||
AfterInput,
|
||||
} = props
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[fieldBaseClass, 'text', className, showError && 'error', readOnly && 'read-only']
|
||||
@@ -63,20 +74,24 @@ const TextInput: React.FC<TextInputProps> = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<Label htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<input
|
||||
data-rtl={rtl}
|
||||
disabled={readOnly}
|
||||
id={`field-${path.replace(/\./g, '__')}`}
|
||||
name={path}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={value || ''}
|
||||
/>
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<div className="input-wrapper">
|
||||
{BeforeInput}
|
||||
<input
|
||||
data-rtl={rtl}
|
||||
disabled={readOnly}
|
||||
id={`field-${path.replace(/\./g, '__')}`}
|
||||
name={path}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={value || ''}
|
||||
/>
|
||||
{AfterInput}
|
||||
</div>
|
||||
<FieldDescription
|
||||
className={`field-description-${path.replace(/\./g, '__')}`}
|
||||
description={description}
|
||||
|
||||
@@ -13,7 +13,17 @@ import TextInput from './Input'
|
||||
const Text: React.FC<Props> = (props) => {
|
||||
const {
|
||||
name,
|
||||
admin: { className, condition, description, placeholder, readOnly, rtl, style, width } = {},
|
||||
admin: {
|
||||
className,
|
||||
condition,
|
||||
description,
|
||||
placeholder,
|
||||
readOnly,
|
||||
rtl,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label, BeforeInput, AfterInput } = {},
|
||||
} = {},
|
||||
inputRef,
|
||||
label,
|
||||
localized,
|
||||
@@ -68,6 +78,10 @@ const Text: React.FC<Props> = (props) => {
|
||||
style={style}
|
||||
value={value}
|
||||
width={width}
|
||||
Error={Error}
|
||||
Label={Label}
|
||||
BeforeInput={BeforeInput}
|
||||
AfterInput={AfterInput}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import type { TextareaField } from '../../../../../fields/config/types'
|
||||
import type { Description } from '../../FieldDescription/types'
|
||||
|
||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import './index.scss'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
|
||||
@@ -28,6 +28,10 @@ export type TextAreaInputProps = Omit<TextareaField, 'type'> & {
|
||||
style?: React.CSSProperties
|
||||
value?: string
|
||||
width?: string
|
||||
Error?: React.ComponentType<any>
|
||||
Label?: React.ComponentType<any>
|
||||
BeforeInput?: React.ReactElement<any>[]
|
||||
AfterInput?: React.ReactElement<any>[]
|
||||
}
|
||||
|
||||
const TextareaInput: React.FC<TextAreaInputProps> = (props) => {
|
||||
@@ -47,10 +51,17 @@ const TextareaInput: React.FC<TextAreaInputProps> = (props) => {
|
||||
style,
|
||||
value,
|
||||
width,
|
||||
Error,
|
||||
Label,
|
||||
BeforeInput,
|
||||
AfterInput,
|
||||
} = props
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
@@ -67,11 +78,12 @@ const TextareaInput: React.FC<TextAreaInputProps> = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<Label htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<label className="textarea-outer" htmlFor={`field-${path.replace(/\./g, '__')}`}>
|
||||
<div className="textarea-inner">
|
||||
<div className="textarea-clone" data-value={value || placeholder || ''} />
|
||||
{BeforeInput}
|
||||
<textarea
|
||||
className="textarea-element"
|
||||
data-rtl={rtl}
|
||||
@@ -83,6 +95,7 @@ const TextareaInput: React.FC<TextAreaInputProps> = (props) => {
|
||||
rows={rows}
|
||||
value={value || ''}
|
||||
/>
|
||||
{AfterInput}
|
||||
</div>
|
||||
</label>
|
||||
<FieldDescription description={description} value={value} />
|
||||
|
||||
@@ -26,6 +26,7 @@ const Textarea: React.FC<Props> = (props) => {
|
||||
rtl,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label, BeforeInput, AfterInput } = {},
|
||||
} = {},
|
||||
label,
|
||||
localized,
|
||||
@@ -82,6 +83,10 @@ const Textarea: React.FC<Props> = (props) => {
|
||||
style={style}
|
||||
value={value as string}
|
||||
width={width}
|
||||
Error={Error}
|
||||
Label={Label}
|
||||
BeforeInput={BeforeInput}
|
||||
AfterInput={AfterInput}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ import { useDocumentDrawer } from '../../../elements/DocumentDrawer'
|
||||
import FileDetails from '../../../elements/FileDetails'
|
||||
import { useListDrawer } from '../../../elements/ListDrawer'
|
||||
import { GetFilterOptions } from '../../../utilities/GetFilterOptions'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import './index.scss'
|
||||
|
||||
@@ -41,6 +41,8 @@ export type UploadInputProps = Omit<UploadField, 'type'> & {
|
||||
style?: React.CSSProperties
|
||||
value?: string
|
||||
width?: string
|
||||
Error?: React.ComponentType<any>
|
||||
Label?: React.ComponentType<any>
|
||||
}
|
||||
|
||||
const UploadInput: React.FC<UploadInputProps> = (props) => {
|
||||
@@ -62,10 +64,15 @@ const UploadInput: React.FC<UploadInputProps> = (props) => {
|
||||
style,
|
||||
value,
|
||||
width,
|
||||
Error,
|
||||
Label,
|
||||
} = props
|
||||
|
||||
const { i18n, t } = useTranslation('fields')
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
const [file, setFile] = useState(undefined)
|
||||
const [missingFile, setMissingFile] = useState(false)
|
||||
const [collectionSlugs] = useState([collection?.slug])
|
||||
@@ -149,8 +156,8 @@ const UploadInput: React.FC<UploadInputProps> = (props) => {
|
||||
setFilterOptionsResult,
|
||||
}}
|
||||
/>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<Label htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
{collection?.upload && (
|
||||
<React.Fragment>
|
||||
{file && !missingFile && (
|
||||
|
||||
@@ -18,7 +18,15 @@ const Upload: React.FC<Props> = (props) => {
|
||||
|
||||
const {
|
||||
name,
|
||||
admin: { className, condition, description, readOnly, style, width } = {},
|
||||
admin: {
|
||||
className,
|
||||
condition,
|
||||
description,
|
||||
readOnly,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label } = {},
|
||||
} = {},
|
||||
fieldTypes,
|
||||
filterOptions,
|
||||
label,
|
||||
@@ -75,6 +83,8 @@ const Upload: React.FC<Props> = (props) => {
|
||||
style={style}
|
||||
value={value as string}
|
||||
width={width}
|
||||
Error={Error}
|
||||
Label={Label}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -293,6 +293,4 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
)
|
||||
}
|
||||
|
||||
type UseAuth<T = User> = () => AuthContext<T>
|
||||
|
||||
export const useAuth: UseAuth = () => useContext(Context)
|
||||
export const useAuth = <T = User,>(): AuthContext<T> => useContext(Context) as AuthContext<T>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { PayloadRequest } from '../../../express/types'
|
||||
import type { Payload } from '../../../payload'
|
||||
|
||||
import formatName from '../../../graphql/utilities/formatName'
|
||||
@@ -18,7 +19,7 @@ const formatConfigNames = (results, configs) => {
|
||||
function accessResolver(payload: Payload) {
|
||||
async function resolver(_, args, context) {
|
||||
const options = {
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
const accessResults = await access(options)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Collection } from '../../../collections/config/types'
|
||||
import type { PayloadRequest } from '../../../express/types'
|
||||
|
||||
import forgotPassword from '../../operations/forgotPassword'
|
||||
|
||||
@@ -11,7 +12,7 @@ function forgotPasswordResolver(collection: Collection): any {
|
||||
},
|
||||
disableEmail: args.disableEmail,
|
||||
expiration: args.expiration,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
await forgotPassword(options)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import type { PayloadRequest } from '../../../express/types'
|
||||
|
||||
import init from '../../operations/init'
|
||||
|
||||
function initResolver(collection: string) {
|
||||
async function resolver(_, args, context) {
|
||||
const options = {
|
||||
collection,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
return init(options)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Collection } from '../../../collections/config/types'
|
||||
import type { PayloadRequest } from '../../../express/types'
|
||||
|
||||
import login from '../../operations/login'
|
||||
|
||||
@@ -11,7 +12,7 @@ function loginResolver(collection: Collection) {
|
||||
password: args.password,
|
||||
},
|
||||
depth: 0,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
res: context.res,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Collection } from '../../../collections/config/types'
|
||||
import type { PayloadRequest } from '../../../express/types'
|
||||
|
||||
import logout from '../../operations/logout'
|
||||
|
||||
@@ -6,7 +7,7 @@ function logoutResolver(collection: Collection): any {
|
||||
async function resolver(_, args, context) {
|
||||
const options = {
|
||||
collection,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
res: context.res,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Collection } from '../../../collections/config/types'
|
||||
import type { PayloadRequest } from '../../../express/types'
|
||||
|
||||
import me from '../../operations/me'
|
||||
|
||||
@@ -7,10 +8,11 @@ function meResolver(collection: Collection): any {
|
||||
const options = {
|
||||
collection,
|
||||
depth: 0,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
return me(options)
|
||||
}
|
||||
|
||||
return resolver
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Collection } from '../../../collections/config/types'
|
||||
import type { PayloadRequest } from '../../../express/types'
|
||||
|
||||
import getExtractJWT from '../../getExtractJWT'
|
||||
import refresh from '../../operations/refresh'
|
||||
@@ -17,7 +18,7 @@ function refreshResolver(collection: Collection) {
|
||||
const options = {
|
||||
collection,
|
||||
depth: 0,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
res: context.res,
|
||||
token,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { Collection } from '../../../collections/config/types'
|
||||
import type { PayloadRequest } from '../../../express/types'
|
||||
|
||||
import resetPassword from '../../operations/resetPassword'
|
||||
|
||||
@@ -13,7 +14,7 @@ function resetPasswordResolver(collection: Collection) {
|
||||
collection,
|
||||
data: args,
|
||||
depth: 0,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
res: context.res,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Collection } from '../../../collections/config/types'
|
||||
import type { PayloadRequest } from '../../../express/types'
|
||||
|
||||
import unlock from '../../operations/unlock'
|
||||
|
||||
@@ -7,12 +8,13 @@ function unlockResolver(collection: Collection) {
|
||||
const options = {
|
||||
collection,
|
||||
data: { email: args.email },
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
const result = await unlock(options)
|
||||
return result
|
||||
}
|
||||
|
||||
return resolver
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { Collection } from '../../../collections/config/types'
|
||||
import type { PayloadRequest } from '../../../express/types'
|
||||
|
||||
import verifyEmail from '../../operations/verifyEmail'
|
||||
|
||||
@@ -11,7 +12,7 @@ function verifyEmailResolver(collection: Collection) {
|
||||
const options = {
|
||||
api: 'GraphQL',
|
||||
collection,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
res: context.res,
|
||||
token: args.token,
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function createResolver<TSlug extends keyof GeneratedTypes['colle
|
||||
data: args.data,
|
||||
depth: 0,
|
||||
draft: args.draft,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
const result = await create(options)
|
||||
|
||||
@@ -30,7 +30,7 @@ export default function getDeleteResolver<TSlug extends keyof GeneratedTypes['co
|
||||
id: args.id,
|
||||
collection,
|
||||
depth: 0,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
const result = await deleteByID(options)
|
||||
|
||||
@@ -18,7 +18,7 @@ export function docAccessResolver(): Resolver {
|
||||
async function resolver(_, args, context) {
|
||||
return docAccess({
|
||||
id: args.id,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function findResolver(collection: Collection): Resolver {
|
||||
draft: args.draft,
|
||||
limit: args.limit,
|
||||
page: args.page,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
sort: args.sort,
|
||||
where: args.where,
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function findVersionByIDResolver(collection: Collection): Resolve
|
||||
collection,
|
||||
depth: 0,
|
||||
draft: args.draft,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
const result = await findVersionByID(options)
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function findVersionsResolver(collection: Collection): Resolver {
|
||||
depth: 0,
|
||||
limit: args.limit,
|
||||
page: args.page,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
sort: args.sort,
|
||||
where: args.where,
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export default function restoreVersionResolver(collection: Collection): Resolver
|
||||
id: args.id,
|
||||
collection,
|
||||
depth: 0,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
const result = await restoreVersion(options)
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function updateResolver<TSlug extends keyof GeneratedTypes['colle
|
||||
data: args.data,
|
||||
depth: 0,
|
||||
draft: args.draft,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
const result = await updateByID<TSlug>(options)
|
||||
|
||||
@@ -134,7 +134,13 @@ export default joi.object({
|
||||
joi.array().items(
|
||||
joi.object().keys({
|
||||
code: joi.string(),
|
||||
label: joi.string(),
|
||||
label: joi
|
||||
.alternatives()
|
||||
.try(
|
||||
joi.object().pattern(joi.string(), [joi.string()]),
|
||||
joi.string(),
|
||||
joi.valid(false),
|
||||
),
|
||||
rtl: joi.boolean(),
|
||||
toString: joi.func(),
|
||||
}),
|
||||
|
||||
@@ -288,7 +288,7 @@ export type Locale = {
|
||||
* label of supported locale
|
||||
* @example "English"
|
||||
*/
|
||||
label: string
|
||||
label: string | Record<string, string>
|
||||
/**
|
||||
* if true, defaults textAligmnent on text fields to RTL
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export { useStepNav } from '../../admin/components/elements/StepNav'
|
||||
export { useTableColumns } from '../../admin/components/elements/TableColumns'
|
||||
export { default as useDebounce } from '../../admin/hooks/useDebounce'
|
||||
export { useDebouncedCallback } from '../../admin/hooks/useDebouncedCallback'
|
||||
export { useDelay } from '../../admin/hooks/useDelay'
|
||||
|
||||
@@ -75,6 +75,12 @@ export const text = baseField.keys({
|
||||
.alternatives()
|
||||
.try(joi.object().pattern(joi.string(), [joi.string()]), joi.string()),
|
||||
rtl: joi.boolean(),
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
BeforeInput: joi.array().items(componentSchema),
|
||||
AfterInput: joi.array().items(componentSchema),
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.string(), joi.func()),
|
||||
maxLength: joi.number(),
|
||||
@@ -88,6 +94,18 @@ export const number = baseField.keys({
|
||||
autoComplete: joi.string(),
|
||||
placeholder: joi.string(),
|
||||
step: joi.number(),
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
BeforeInput: joi
|
||||
.array()
|
||||
.items(componentSchema)
|
||||
.when('hasMany', { not: true, otherwise: joi.forbidden() }),
|
||||
AfterInput: joi
|
||||
.array()
|
||||
.items(componentSchema)
|
||||
.when('hasMany', { not: true, otherwise: joi.forbidden() }),
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.number(), joi.func()),
|
||||
hasMany: joi.boolean().default(false),
|
||||
@@ -104,6 +122,12 @@ export const textarea = baseField.keys({
|
||||
placeholder: joi.string(),
|
||||
rows: joi.number(),
|
||||
rtl: joi.boolean(),
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
BeforeInput: joi.array().items(componentSchema),
|
||||
AfterInput: joi.array().items(componentSchema),
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.string(), joi.func()),
|
||||
maxLength: joi.number(),
|
||||
@@ -116,6 +140,12 @@ export const email = baseField.keys({
|
||||
admin: baseAdminFields.keys({
|
||||
autoComplete: joi.string(),
|
||||
placeholder: joi.string(),
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
BeforeInput: joi.array().items(componentSchema),
|
||||
AfterInput: joi.array().items(componentSchema),
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.string(), joi.func()),
|
||||
maxLength: joi.number(),
|
||||
@@ -128,6 +158,10 @@ export const code = baseField.keys({
|
||||
admin: baseAdminFields.keys({
|
||||
editorOptions: joi.object().unknown(), // Editor['options'] @monaco-editor/react
|
||||
language: joi.string(),
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.string(), joi.func()),
|
||||
type: joi.string().valid('code').required(),
|
||||
@@ -135,6 +169,12 @@ export const code = baseField.keys({
|
||||
|
||||
export const json = baseField.keys({
|
||||
name: joi.string().required(),
|
||||
admin: baseAdminFields.keys({
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.array(), joi.object()),
|
||||
type: joi.string().valid('json').required(),
|
||||
})
|
||||
@@ -144,6 +184,10 @@ export const select = baseField.keys({
|
||||
admin: baseAdminFields.keys({
|
||||
isClearable: joi.boolean().default(false),
|
||||
isSortable: joi.boolean().default(false),
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi
|
||||
.alternatives()
|
||||
@@ -171,6 +215,10 @@ export const radio = baseField.keys({
|
||||
name: joi.string().required(),
|
||||
admin: baseAdminFields.keys({
|
||||
layout: joi.string().valid('vertical', 'horizontal'),
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.string().allow(''), joi.func()),
|
||||
options: joi
|
||||
@@ -268,6 +316,12 @@ export const array = baseField.keys({
|
||||
|
||||
export const upload = baseField.keys({
|
||||
name: joi.string().required(),
|
||||
admin: baseAdminFields.keys({
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.object(), joi.func()),
|
||||
filterOptions: joi.alternatives().try(joi.object(), joi.func()),
|
||||
maxDepth: joi.number(),
|
||||
@@ -277,12 +331,28 @@ export const upload = baseField.keys({
|
||||
|
||||
export const checkbox = baseField.keys({
|
||||
name: joi.string().required(),
|
||||
admin: baseAdminFields.keys({
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
BeforeInput: joi.array().items(componentSchema),
|
||||
AfterInput: joi.array().items(componentSchema),
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.boolean(), joi.func()),
|
||||
type: joi.string().valid('checkbox').required(),
|
||||
})
|
||||
|
||||
export const point = baseField.keys({
|
||||
name: joi.string().required(),
|
||||
admin: baseAdminFields.keys({
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
BeforeInput: joi.array().items(componentSchema),
|
||||
AfterInput: joi.array().items(componentSchema),
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.array().items(joi.number()).max(2).min(2), joi.func()),
|
||||
type: joi.string().valid('point').required(),
|
||||
})
|
||||
@@ -292,6 +362,10 @@ export const relationship = baseField.keys({
|
||||
admin: baseAdminFields.keys({
|
||||
allowCreate: joi.boolean().default(true),
|
||||
isSortable: joi.boolean().default(false),
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.func()),
|
||||
filterOptions: joi.alternatives().try(joi.object(), joi.func()),
|
||||
@@ -382,6 +456,12 @@ export const date = baseField.keys({
|
||||
timeIntervals: joi.number(),
|
||||
}),
|
||||
placeholder: joi.string(),
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
BeforeInput: joi.array().items(componentSchema),
|
||||
AfterInput: joi.array().items(componentSchema),
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.string(), joi.func()),
|
||||
type: joi.string().valid('date').required(),
|
||||
|
||||
@@ -15,6 +15,8 @@ import type { PayloadRequest, RequestContext } from '../../express/types'
|
||||
import type { SanitizedGlobalConfig } from '../../globals/config/types'
|
||||
import type { Payload } from '../../payload'
|
||||
import type { Operation, Where } from '../../types'
|
||||
import type { Props as ErrorProps } from '../../admin/components/forms/Error/types'
|
||||
import type { Props as LabelProps } from '../../admin/components/forms/Label/types'
|
||||
|
||||
export type FieldHookArgs<T extends TypeWithID = any, P = any, S = any> = {
|
||||
/** The collection which the field belongs to. If the field belongs to a global, this will be null. */
|
||||
@@ -158,6 +160,12 @@ export type NumberField = FieldBase & {
|
||||
placeholder?: Record<string, string> | string
|
||||
/** Set a value for the number field to increment / decrement using browser controls. */
|
||||
step?: number
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
BeforeInput?: React.ReactElement<any>[]
|
||||
AfterInput?: React.ReactElement<any>[]
|
||||
}
|
||||
}
|
||||
/** Maximum value accepted. Used in the default `validation` function. */
|
||||
max?: number
|
||||
@@ -188,6 +196,12 @@ export type TextField = FieldBase & {
|
||||
autoComplete?: string
|
||||
placeholder?: Record<string, string> | string
|
||||
rtl?: boolean
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
BeforeInput?: React.ReactElement<any>[]
|
||||
AfterInput?: React.ReactElement<any>[]
|
||||
}
|
||||
}
|
||||
maxLength?: number
|
||||
minLength?: number
|
||||
@@ -198,6 +212,12 @@ export type EmailField = FieldBase & {
|
||||
admin?: Admin & {
|
||||
autoComplete?: string
|
||||
placeholder?: Record<string, string> | string
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
BeforeInput?: React.ReactElement<any>[]
|
||||
AfterInput?: React.ReactElement<any>[]
|
||||
}
|
||||
}
|
||||
type: 'email'
|
||||
}
|
||||
@@ -207,6 +227,12 @@ export type TextareaField = FieldBase & {
|
||||
placeholder?: Record<string, string> | string
|
||||
rows?: number
|
||||
rtl?: boolean
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
BeforeInput?: React.ReactElement<any>[]
|
||||
AfterInput?: React.ReactElement<any>[]
|
||||
}
|
||||
}
|
||||
maxLength?: number
|
||||
minLength?: number
|
||||
@@ -215,12 +241,26 @@ export type TextareaField = FieldBase & {
|
||||
|
||||
export type CheckboxField = FieldBase & {
|
||||
type: 'checkbox'
|
||||
admin?: Admin & {
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
BeforeInput?: React.ReactElement<any>[]
|
||||
AfterInput?: React.ReactElement<any>[]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type DateField = FieldBase & {
|
||||
admin?: Admin & {
|
||||
date?: ConditionalDateProps
|
||||
placeholder?: Record<string, string> | string
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
BeforeInput?: React.ReactElement<any>[]
|
||||
AfterInput?: React.ReactElement<any>[]
|
||||
}
|
||||
}
|
||||
type: 'date'
|
||||
}
|
||||
@@ -315,6 +355,12 @@ export type UIField = {
|
||||
}
|
||||
|
||||
export type UploadField = FieldBase & {
|
||||
admin?: {
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
}
|
||||
}
|
||||
filterOptions?: FilterOptions
|
||||
maxDepth?: number
|
||||
relationTo: string
|
||||
@@ -324,6 +370,10 @@ export type UploadField = FieldBase & {
|
||||
type CodeAdmin = Admin & {
|
||||
editorOptions?: EditorProps['options']
|
||||
language?: string
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
}
|
||||
}
|
||||
|
||||
export type CodeField = Omit<FieldBase, 'admin'> & {
|
||||
@@ -335,6 +385,10 @@ export type CodeField = Omit<FieldBase, 'admin'> & {
|
||||
|
||||
type JSONAdmin = Admin & {
|
||||
editorOptions?: EditorProps['options']
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
}
|
||||
}
|
||||
|
||||
export type JSONField = Omit<FieldBase, 'admin'> & {
|
||||
@@ -346,6 +400,10 @@ export type SelectField = FieldBase & {
|
||||
admin?: Admin & {
|
||||
isClearable?: boolean
|
||||
isSortable?: boolean
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
}
|
||||
}
|
||||
hasMany?: boolean
|
||||
options: Option[]
|
||||
@@ -356,6 +414,10 @@ export type RelationshipField = FieldBase & {
|
||||
admin?: Admin & {
|
||||
allowCreate?: boolean
|
||||
isSortable?: boolean
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
}
|
||||
}
|
||||
filterOptions?: FilterOptions
|
||||
hasMany?: boolean
|
||||
@@ -440,6 +502,10 @@ export type ArrayField = FieldBase & {
|
||||
export type RadioField = FieldBase & {
|
||||
admin?: Admin & {
|
||||
layout?: 'horizontal' | 'vertical'
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
}
|
||||
}
|
||||
options: Option[]
|
||||
type: 'radio'
|
||||
|
||||
@@ -16,7 +16,7 @@ export function docAccessResolver(global: SanitizedGlobalConfig): Resolver {
|
||||
async function resolver(_, context) {
|
||||
return docAccess({
|
||||
globalConfig: global,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
import type { Document } from '../../../types'
|
||||
import type { Document, PayloadRequest } from '../../../types'
|
||||
import type { SanitizedGlobalConfig } from '../../config/types'
|
||||
|
||||
import findOne from '../../operations/findOne'
|
||||
@@ -16,7 +16,7 @@ export default function findOneResolver(globalConfig: SanitizedGlobalConfig): Do
|
||||
depth: 0,
|
||||
draft: args.draft,
|
||||
globalConfig,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
slug,
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function findVersionByIDResolver(globalConfig: SanitizedGlobalCon
|
||||
depth: 0,
|
||||
draft: args.draft,
|
||||
globalConfig,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
const result = await findVersionByID(options)
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function findVersionsResolver(globalConfig: SanitizedGlobalConfig
|
||||
globalConfig,
|
||||
limit: args.limit,
|
||||
page: args.page,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
sort: args.sort,
|
||||
where: args.where,
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function restoreVersionResolver(globalConfig: SanitizedGlobalConf
|
||||
id: args.id,
|
||||
depth: 0,
|
||||
globalConfig,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
const result = await restoreVersion(options)
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function updateResolver<TSlug extends keyof GeneratedTypes['globa
|
||||
depth: 0,
|
||||
draft: args.draft,
|
||||
globalConfig,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
slug,
|
||||
}
|
||||
|
||||
|
||||
@@ -17,12 +17,27 @@ export const fieldSchemaToJSON = (fields: Field[]): FieldSchemaJSON => {
|
||||
|
||||
switch (field.type) {
|
||||
case 'group':
|
||||
case 'array':
|
||||
acc.push({
|
||||
name: field.name,
|
||||
fields: fieldSchemaToJSON(field.fields),
|
||||
type: field.type,
|
||||
})
|
||||
|
||||
break
|
||||
|
||||
case 'array':
|
||||
acc.push({
|
||||
name: field.name,
|
||||
fields: fieldSchemaToJSON([
|
||||
...field.fields,
|
||||
{
|
||||
name: 'id',
|
||||
type: 'text',
|
||||
},
|
||||
]),
|
||||
type: field.type,
|
||||
})
|
||||
|
||||
break
|
||||
|
||||
case 'blocks':
|
||||
@@ -30,19 +45,25 @@ export const fieldSchemaToJSON = (fields: Field[]): FieldSchemaJSON => {
|
||||
name: field.name,
|
||||
blocks: field.blocks.reduce((acc, block) => {
|
||||
acc[block.slug] = {
|
||||
fields: fieldSchemaToJSON(block.fields),
|
||||
fields: fieldSchemaToJSON([
|
||||
...block.fields,
|
||||
{
|
||||
name: 'id',
|
||||
type: 'text',
|
||||
},
|
||||
]),
|
||||
}
|
||||
|
||||
return acc
|
||||
}, {}),
|
||||
type: field.type,
|
||||
})
|
||||
|
||||
break
|
||||
|
||||
case 'row':
|
||||
case 'collapsible':
|
||||
result = result.concat(fieldSchemaToJSON(field.fields))
|
||||
|
||||
break
|
||||
|
||||
case 'tabs': {
|
||||
|
||||
@@ -85,7 +85,22 @@ export default buildConfigWithDefaults({
|
||||
},
|
||||
localization: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en', 'es'],
|
||||
locales: [
|
||||
{
|
||||
label: {
|
||||
es: 'Español',
|
||||
en: 'Spanish',
|
||||
},
|
||||
code: 'es',
|
||||
},
|
||||
{
|
||||
label: {
|
||||
es: 'Inglés',
|
||||
en: 'English',
|
||||
},
|
||||
code: 'en',
|
||||
},
|
||||
],
|
||||
},
|
||||
collections: [
|
||||
Posts,
|
||||
|
||||
@@ -488,6 +488,43 @@ describe('admin', () => {
|
||||
'Home',
|
||||
)
|
||||
})
|
||||
|
||||
test('should allow custom translation of locale labels', async () => {
|
||||
const selectOptionClass = '.localizer .popup-button-list__button'
|
||||
const localizorButton = page.locator('.localizer .popup-button')
|
||||
const secondLocale = page.locator(selectOptionClass).nth(1)
|
||||
|
||||
async function checkLocalLabels(firstLabel: string, secondLabel: string) {
|
||||
await localizorButton.click()
|
||||
await expect(page.locator(selectOptionClass).first()).toContainText(firstLabel)
|
||||
await expect(page.locator(selectOptionClass).nth(1)).toContainText(secondLabel)
|
||||
}
|
||||
|
||||
await checkLocalLabels('English (en)', 'Spanish (es)')
|
||||
|
||||
// Change locale to Spanish
|
||||
await localizorButton.click()
|
||||
await expect(secondLocale).toContainText('Spanish (es)')
|
||||
await secondLocale.click()
|
||||
|
||||
// Go to account page
|
||||
await page.goto(url.account)
|
||||
|
||||
const languageField = page.locator('.payload-settings__language .react-select')
|
||||
const options = page.locator('.rs__option')
|
||||
|
||||
// Change language to Spanish
|
||||
await languageField.click()
|
||||
await options.locator('text=Español').click()
|
||||
|
||||
await checkLocalLabels('Inglés (en)', 'Español (es)')
|
||||
|
||||
// Change locale and language back to English
|
||||
await languageField.click()
|
||||
await options.locator('text=English').click()
|
||||
await localizorButton.click()
|
||||
await expect(secondLocale).toContainText('Spanish (es)')
|
||||
})
|
||||
})
|
||||
|
||||
describe('list view', () => {
|
||||
@@ -647,6 +684,38 @@ describe('admin', () => {
|
||||
await expect(page.locator(tableRowLocator)).toHaveCount(2)
|
||||
})
|
||||
|
||||
test('resets filter value and operator on field update', async () => {
|
||||
const { id } = await createPost({ title: 'post1' })
|
||||
await createPost({ title: 'post2' })
|
||||
|
||||
// open the column controls
|
||||
await page.locator('.list-controls__toggle-columns').click()
|
||||
await page.locator('.list-controls__toggle-where').click()
|
||||
await page.waitForSelector('.list-controls__where.rah-static--height-auto')
|
||||
await page.locator('.where-builder__add-first-filter').click()
|
||||
|
||||
const operatorField = page.locator('.condition__operator')
|
||||
await operatorField.click()
|
||||
|
||||
const dropdownOperatorOptions = operatorField.locator('.rs__option')
|
||||
await dropdownOperatorOptions.locator('text=equals').click()
|
||||
|
||||
// execute filter (where ID equals id value)
|
||||
const valueField = page.locator('.condition__value > input')
|
||||
await valueField.fill(id)
|
||||
|
||||
const filterField = page.locator('.condition__field')
|
||||
await filterField.click()
|
||||
|
||||
// select new filter field of Number
|
||||
const dropdownFieldOptions = filterField.locator('.rs__option')
|
||||
await dropdownFieldOptions.locator('text=Number').click()
|
||||
|
||||
// expect operator & value field to reset (be empty)
|
||||
await expect(operatorField.locator('.rs__placeholder')).toContainText('Select a value')
|
||||
await expect(valueField).toHaveValue('')
|
||||
})
|
||||
|
||||
test('should accept where query from valid URL where parameter', async () => {
|
||||
await createPost({ title: 'post1' })
|
||||
await createPost({ title: 'post2' })
|
||||
|
||||
@@ -12,14 +12,13 @@ export interface Relation {
|
||||
|
||||
const openAccess = {
|
||||
create: () => true,
|
||||
delete: () => true,
|
||||
read: () => true,
|
||||
update: () => true,
|
||||
delete: () => true,
|
||||
}
|
||||
|
||||
const collectionWithName = (collectionSlug: string): CollectionConfig => {
|
||||
return {
|
||||
slug: collectionSlug,
|
||||
access: openAccess,
|
||||
fields: [
|
||||
{
|
||||
@@ -27,6 +26,7 @@ const collectionWithName = (collectionSlug: string): CollectionConfig => {
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
slug: collectionSlug,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,47 +35,27 @@ export const relationSlug = 'relation'
|
||||
|
||||
export const pointSlug = 'point'
|
||||
|
||||
export const errorOnHookSlug = 'error-on-hooks'
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
graphQL: {
|
||||
schemaOutputFile: path.resolve(__dirname, 'schema.graphql'),
|
||||
queries: (GraphQL) => {
|
||||
return {
|
||||
QueryWithInternalError: {
|
||||
type: new GraphQL.GraphQLObjectType({
|
||||
name: 'QueryWithInternalError',
|
||||
fields: {
|
||||
text: {
|
||||
type: GraphQL.GraphQLString,
|
||||
},
|
||||
},
|
||||
}),
|
||||
resolve: () => {
|
||||
// Throwing an internal error with potentially sensitive data
|
||||
throw new Error('Lost connection to the Pentagon. Secret data: ******')
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
collections: [
|
||||
{
|
||||
slug: 'users',
|
||||
auth: true,
|
||||
access: openAccess,
|
||||
auth: true,
|
||||
fields: [],
|
||||
slug: 'users',
|
||||
},
|
||||
{
|
||||
slug: pointSlug,
|
||||
access: openAccess,
|
||||
fields: [
|
||||
{
|
||||
type: 'point',
|
||||
name: 'point',
|
||||
type: 'point',
|
||||
},
|
||||
],
|
||||
slug: pointSlug,
|
||||
},
|
||||
{
|
||||
slug,
|
||||
access: openAccess,
|
||||
fields: [
|
||||
{
|
||||
@@ -92,173 +72,173 @@ export default buildConfigWithDefaults({
|
||||
},
|
||||
{
|
||||
name: 'min',
|
||||
type: 'number',
|
||||
min: 10,
|
||||
type: 'number',
|
||||
},
|
||||
// Relationship
|
||||
{
|
||||
name: 'relationField',
|
||||
type: 'relationship',
|
||||
relationTo: relationSlug,
|
||||
type: 'relationship',
|
||||
},
|
||||
{
|
||||
name: 'relationToCustomID',
|
||||
type: 'relationship',
|
||||
relationTo: 'custom-ids',
|
||||
type: 'relationship',
|
||||
},
|
||||
// Relation hasMany
|
||||
{
|
||||
name: 'relationHasManyField',
|
||||
type: 'relationship',
|
||||
relationTo: relationSlug,
|
||||
hasMany: true,
|
||||
relationTo: relationSlug,
|
||||
type: 'relationship',
|
||||
},
|
||||
// Relation multiple relationTo
|
||||
{
|
||||
name: 'relationMultiRelationTo',
|
||||
type: 'relationship',
|
||||
relationTo: [relationSlug, 'dummy'],
|
||||
type: 'relationship',
|
||||
},
|
||||
// Relation multiple relationTo hasMany
|
||||
{
|
||||
name: 'relationMultiRelationToHasMany',
|
||||
type: 'relationship',
|
||||
relationTo: [relationSlug, 'dummy'],
|
||||
hasMany: true,
|
||||
relationTo: [relationSlug, 'dummy'],
|
||||
type: 'relationship',
|
||||
},
|
||||
{
|
||||
name: 'A1',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'A2',
|
||||
defaultValue: 'textInRowInGroup',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
type: 'group',
|
||||
},
|
||||
{
|
||||
name: 'B1',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
type: 'collapsible',
|
||||
label: 'Collapsible',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'B2',
|
||||
defaultValue: 'textInRowInGroup',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
label: 'Collapsible',
|
||||
type: 'collapsible',
|
||||
},
|
||||
],
|
||||
type: 'group',
|
||||
},
|
||||
{
|
||||
name: 'C1',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'C2Text',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
type: 'collapsible',
|
||||
label: 'Collapsible2',
|
||||
fields: [
|
||||
{
|
||||
name: 'C2',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
type: 'collapsible',
|
||||
label: 'Collapsible2',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'C3',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
label: 'Collapsible2',
|
||||
type: 'collapsible',
|
||||
},
|
||||
],
|
||||
type: 'row',
|
||||
},
|
||||
],
|
||||
type: 'group',
|
||||
},
|
||||
],
|
||||
label: 'Collapsible2',
|
||||
type: 'collapsible',
|
||||
},
|
||||
],
|
||||
type: 'row',
|
||||
},
|
||||
],
|
||||
type: 'group',
|
||||
},
|
||||
{
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
{
|
||||
label: 'Tab1',
|
||||
name: 'D1',
|
||||
fields: [
|
||||
{
|
||||
name: 'D2',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
type: 'collapsible',
|
||||
label: 'Collapsible2',
|
||||
fields: [
|
||||
{
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
{
|
||||
label: 'Tab1',
|
||||
fields: [
|
||||
{
|
||||
name: 'D3',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
type: 'collapsible',
|
||||
label: 'Collapsible2',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'D4',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
label: 'Collapsible2',
|
||||
type: 'collapsible',
|
||||
},
|
||||
],
|
||||
type: 'row',
|
||||
},
|
||||
],
|
||||
type: 'group',
|
||||
},
|
||||
],
|
||||
label: 'Tab1',
|
||||
},
|
||||
],
|
||||
type: 'tabs',
|
||||
},
|
||||
],
|
||||
label: 'Collapsible2',
|
||||
type: 'collapsible',
|
||||
},
|
||||
],
|
||||
type: 'row',
|
||||
},
|
||||
],
|
||||
type: 'group',
|
||||
},
|
||||
],
|
||||
label: 'Tab1',
|
||||
},
|
||||
],
|
||||
type: 'tabs',
|
||||
},
|
||||
],
|
||||
slug,
|
||||
},
|
||||
{
|
||||
slug: 'custom-ids',
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
@@ -272,47 +252,98 @@ export default buildConfigWithDefaults({
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
slug: 'custom-ids',
|
||||
},
|
||||
collectionWithName(relationSlug),
|
||||
collectionWithName('dummy'),
|
||||
{
|
||||
slug: 'payload-api-test-ones',
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
...collectionWithName(errorOnHookSlug),
|
||||
fields: [
|
||||
{
|
||||
name: 'payloadAPI',
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
hooks: {
|
||||
afterRead: [({ req }) => req.payloadAPI],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'errorBeforeChange',
|
||||
type: 'checkbox',
|
||||
},
|
||||
],
|
||||
hooks: {
|
||||
afterDelete: [
|
||||
({ doc }) => {
|
||||
if (doc?.errorAfterDelete) {
|
||||
throw new Error('Error After Delete Thrown')
|
||||
}
|
||||
},
|
||||
],
|
||||
beforeChange: [
|
||||
({ originalDoc }) => {
|
||||
if (originalDoc?.errorBeforeChange) {
|
||||
throw new Error('Error Before Change Thrown')
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
slug: 'payload-api-test-twos',
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'payloadAPI',
|
||||
type: 'text',
|
||||
hooks: {
|
||||
afterRead: [({ req }) => req.payloadAPI],
|
||||
},
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
slug: 'payload-api-test-ones',
|
||||
},
|
||||
{
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'payloadAPI',
|
||||
hooks: {
|
||||
afterRead: [({ req }) => req.payloadAPI],
|
||||
},
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'relation',
|
||||
type: 'relationship',
|
||||
relationTo: 'payload-api-test-ones',
|
||||
type: 'relationship',
|
||||
},
|
||||
],
|
||||
slug: 'payload-api-test-twos',
|
||||
},
|
||||
],
|
||||
graphQL: {
|
||||
queries: (GraphQL) => {
|
||||
return {
|
||||
QueryWithInternalError: {
|
||||
resolve: () => {
|
||||
// Throwing an internal error with potentially sensitive data
|
||||
throw new Error('Lost connection to the Pentagon. Secret data: ******')
|
||||
},
|
||||
type: new GraphQL.GraphQLObjectType({
|
||||
name: 'QueryWithInternalError',
|
||||
fields: {
|
||||
text: {
|
||||
type: GraphQL.GraphQLString,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
},
|
||||
schemaOutputFile: path.resolve(__dirname, 'schema.graphql'),
|
||||
},
|
||||
onInit: async (payload) => {
|
||||
const user = await payload.create({
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: devUser.email,
|
||||
@@ -331,8 +362,8 @@ export default buildConfigWithDefaults({
|
||||
await payload.create({
|
||||
collection: slug,
|
||||
data: {
|
||||
title: 'has custom ID relation',
|
||||
relationToCustomID: 1,
|
||||
title: 'has custom ID relation',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -353,23 +384,23 @@ export default buildConfigWithDefaults({
|
||||
await payload.create({
|
||||
collection: slug,
|
||||
data: {
|
||||
title: 'with-description',
|
||||
description: 'description',
|
||||
title: 'with-description',
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: slug,
|
||||
data: {
|
||||
title: 'numPost1',
|
||||
number: 1,
|
||||
title: 'numPost1',
|
||||
},
|
||||
})
|
||||
await payload.create({
|
||||
collection: slug,
|
||||
data: {
|
||||
title: 'numPost2',
|
||||
number: 2,
|
||||
title: 'numPost2',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -390,15 +421,15 @@ export default buildConfigWithDefaults({
|
||||
await payload.create({
|
||||
collection: slug,
|
||||
data: {
|
||||
title: 'rel to hasMany',
|
||||
relationHasManyField: rel1.id,
|
||||
title: 'rel to hasMany',
|
||||
},
|
||||
})
|
||||
await payload.create({
|
||||
collection: slug,
|
||||
data: {
|
||||
title: 'rel to hasMany 2',
|
||||
relationHasManyField: rel2.id,
|
||||
title: 'rel to hasMany 2',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -406,11 +437,11 @@ export default buildConfigWithDefaults({
|
||||
await payload.create({
|
||||
collection: slug,
|
||||
data: {
|
||||
title: 'rel to multi',
|
||||
relationMultiRelationTo: {
|
||||
relationTo: relationSlug,
|
||||
value: rel2.id,
|
||||
},
|
||||
title: 'rel to multi',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -418,7 +449,6 @@ export default buildConfigWithDefaults({
|
||||
await payload.create({
|
||||
collection: slug,
|
||||
data: {
|
||||
title: 'rel to multi hasMany',
|
||||
relationMultiRelationToHasMany: [
|
||||
{
|
||||
relationTo: relationSlug,
|
||||
@@ -429,6 +459,7 @@ export default buildConfigWithDefaults({
|
||||
value: rel2.id,
|
||||
},
|
||||
],
|
||||
title: 'rel to multi hasMany',
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ import type { Post } from './payload-types'
|
||||
import payload from '../../packages/payload/src'
|
||||
import { mapAsync } from '../../packages/payload/src/utilities/mapAsync'
|
||||
import { initPayloadTest } from '../helpers/configHelpers'
|
||||
import configPromise, { pointSlug, slug } from './config'
|
||||
import { idToString } from '../helpers/idToString'
|
||||
import configPromise, { errorOnHookSlug, pointSlug, slug } from './config'
|
||||
|
||||
const title = 'title'
|
||||
|
||||
@@ -42,8 +43,7 @@ describe('collections-graphql', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
existingDoc = await createPost()
|
||||
existingDocGraphQLID =
|
||||
payload.db.defaultIDType === 'number' ? existingDoc.id : `"${existingDoc.id}"`
|
||||
existingDocGraphQLID = idToString(existingDoc.id, payload)
|
||||
})
|
||||
|
||||
it('should create', async () => {
|
||||
@@ -67,7 +67,7 @@ describe('collections-graphql', () => {
|
||||
title
|
||||
}
|
||||
}`
|
||||
const response = await client.request(query, { title })
|
||||
const response = (await client.request(query, { title })) as any
|
||||
const doc: Post = response.createPost
|
||||
|
||||
expect(doc).toMatchObject({ title })
|
||||
@@ -102,6 +102,92 @@ describe('collections-graphql', () => {
|
||||
expect(docs).toContainEqual(expect.objectContaining({ id: existingDoc.id }))
|
||||
})
|
||||
|
||||
it('should read using multiple queries', async () => {
|
||||
const query = `query {
|
||||
postIDs: Posts {
|
||||
docs {
|
||||
id
|
||||
}
|
||||
}
|
||||
posts: Posts {
|
||||
docs {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
}`
|
||||
const response = await client.request(query)
|
||||
const { postIDs, posts } = response
|
||||
expect(postIDs.docs).toBeDefined()
|
||||
expect(posts.docs).toBeDefined()
|
||||
})
|
||||
|
||||
it('should commit or rollback multiple mutations independently', async () => {
|
||||
const firstTitle = 'first title'
|
||||
const secondTitle = 'second title'
|
||||
const first = await payload.create({
|
||||
collection: errorOnHookSlug,
|
||||
data: {
|
||||
errorBeforeChange: true,
|
||||
title: firstTitle,
|
||||
},
|
||||
})
|
||||
const second = await payload.create({
|
||||
collection: errorOnHookSlug,
|
||||
data: {
|
||||
errorBeforeChange: true,
|
||||
title: secondTitle,
|
||||
},
|
||||
})
|
||||
|
||||
const updated = 'updated title'
|
||||
|
||||
const query = `mutation {
|
||||
createPost(data: {title: "${title}"}) {
|
||||
id
|
||||
title
|
||||
}
|
||||
updateFirst: updateErrorOnHook(id: ${idToString(
|
||||
first.id,
|
||||
payload,
|
||||
)}, data: {title: "${updated}"}) {
|
||||
title
|
||||
}
|
||||
updateSecond: updateErrorOnHook(id: ${idToString(
|
||||
second.id,
|
||||
payload,
|
||||
)}, data: {title: "${updated}"}) {
|
||||
id
|
||||
title
|
||||
}
|
||||
}`
|
||||
|
||||
client.requestConfig.errorPolicy = 'all'
|
||||
const response = await client.request(query)
|
||||
client.requestConfig.errorPolicy = 'none'
|
||||
|
||||
const createdResult = await payload.findByID({
|
||||
id: response.createPost.id,
|
||||
collection: slug,
|
||||
})
|
||||
const updateFirstResult = await payload.findByID({
|
||||
id: first.id,
|
||||
collection: errorOnHookSlug,
|
||||
})
|
||||
const updateSecondResult = await payload.findByID({
|
||||
id: second.id,
|
||||
collection: errorOnHookSlug,
|
||||
})
|
||||
|
||||
expect(response?.createPost.id).toBeDefined()
|
||||
expect(response?.updateFirst).toBeNull()
|
||||
expect(response?.updateSecond).toBeNull()
|
||||
|
||||
expect(createdResult).toMatchObject(response.createPost)
|
||||
expect(updateFirstResult).toMatchObject(first)
|
||||
expect(updateSecondResult).toStrictEqual(second)
|
||||
})
|
||||
|
||||
it('should retain payload api', async () => {
|
||||
const query = `
|
||||
query {
|
||||
@@ -685,11 +771,11 @@ describe('collections-graphql', () => {
|
||||
|
||||
// language=graphQL
|
||||
const query = `query {
|
||||
Posts(where: { title: { exists: true }}) {
|
||||
docs {
|
||||
badFieldName
|
||||
Posts(where: { title: { exists: true }}) {
|
||||
docs {
|
||||
badFieldName
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
await client.request(query).catch((err) => {
|
||||
error = err
|
||||
@@ -702,12 +788,12 @@ describe('collections-graphql', () => {
|
||||
let error
|
||||
// language=graphQL
|
||||
const query = `mutation {
|
||||
createPost(data: {min: 1}) {
|
||||
id
|
||||
min
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
createPost(data: {min: 1}) {
|
||||
id
|
||||
min
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}`
|
||||
|
||||
await client.request(query).catch((err) => {
|
||||
@@ -722,21 +808,21 @@ describe('collections-graphql', () => {
|
||||
let error
|
||||
// language=graphQL
|
||||
const query = `mutation createTest {
|
||||
test1:createUser(data: { email: "test@test.com", password: "test" }) {
|
||||
email
|
||||
}
|
||||
test1:createUser(data: { email: "test@test.com", password: "test" }) {
|
||||
email
|
||||
}
|
||||
|
||||
test2:createUser(data: { email: "test2@test.com", password: "" }) {
|
||||
email
|
||||
}
|
||||
test2:createUser(data: { email: "test2@test.com", password: "" }) {
|
||||
email
|
||||
}
|
||||
|
||||
test3:createUser(data: { email: "test@test.com", password: "test" }) {
|
||||
email
|
||||
}
|
||||
test3:createUser(data: { email: "test@test.com", password: "test" }) {
|
||||
email
|
||||
}
|
||||
|
||||
test4:createUser(data: { email: "", password: "test" }) {
|
||||
email
|
||||
}
|
||||
test4:createUser(data: { email: "", password: "test" }) {
|
||||
email
|
||||
}
|
||||
}`
|
||||
|
||||
await client.request(query).catch((err) => {
|
||||
@@ -775,9 +861,9 @@ describe('collections-graphql', () => {
|
||||
let error
|
||||
// language=graphQL
|
||||
const query = `query {
|
||||
QueryWithInternalError {
|
||||
text
|
||||
}
|
||||
QueryWithInternalError {
|
||||
text
|
||||
}
|
||||
}`
|
||||
|
||||
await client.request(query).catch((err) => {
|
||||
|
||||
@@ -14,6 +14,12 @@ const RelationshipFields: CollectionConfig = {
|
||||
required: true,
|
||||
type: 'relationship',
|
||||
},
|
||||
{
|
||||
name: 'relationHasManyPolymorphic',
|
||||
type: 'relationship',
|
||||
relationTo: ['text-fields', 'array-fields'],
|
||||
hasMany: true,
|
||||
},
|
||||
{
|
||||
name: 'relationToSelf',
|
||||
relationTo: relationshipFieldsSlug,
|
||||
|
||||
9
test/fields/collections/Text/AfterInput.tsx
Normal file
9
test/fields/collections/Text/AfterInput.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
const AfterInputImpl: React.FC = () => {
|
||||
return <label className="after-input">#after-input</label>
|
||||
}
|
||||
|
||||
export const AfterInput = <AfterInputImpl />
|
||||
9
test/fields/collections/Text/BeforeInput.tsx
Normal file
9
test/fields/collections/Text/BeforeInput.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
const BeforeInputImpl: React.FC = () => {
|
||||
return <label className="before-input">#before-input</label>
|
||||
}
|
||||
|
||||
export const BeforeInput = <BeforeInputImpl />
|
||||
15
test/fields/collections/Text/CustomError.tsx
Normal file
15
test/fields/collections/Text/CustomError.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react'
|
||||
|
||||
import type { Props } from '../../../../packages/payload/src/admin/components/forms/Error/types'
|
||||
|
||||
const CustomError: React.FC<Props> = (props) => {
|
||||
const { showError = false } = props
|
||||
|
||||
if (showError) {
|
||||
return <div className="custom-error">#custom-error</div>
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export default CustomError
|
||||
13
test/fields/collections/Text/CustomLabel.tsx
Normal file
13
test/fields/collections/Text/CustomLabel.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
const CustomLabel: React.FC<{ htmlFor: string }> = ({ htmlFor }) => {
|
||||
return (
|
||||
<label htmlFor={htmlFor} className="custom-label">
|
||||
#label
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomLabel
|
||||
@@ -1,51 +1,51 @@
|
||||
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
|
||||
|
||||
import { textFieldsSlug } from '../../slugs'
|
||||
|
||||
export const defaultText = 'default-text'
|
||||
import { AfterInput } from './AfterInput'
|
||||
import { BeforeInput } from './BeforeInput'
|
||||
import CustomError from './CustomError'
|
||||
import CustomLabel from './CustomLabel'
|
||||
import { defaultText, textFieldsSlug } from './shared'
|
||||
|
||||
const TextFields: CollectionConfig = {
|
||||
slug: textFieldsSlug,
|
||||
admin: {
|
||||
useAsTitle: 'text',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
required: true,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'localizedText',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'i18nText',
|
||||
type: 'text',
|
||||
label: {
|
||||
en: 'Text en',
|
||||
es: 'Text es',
|
||||
},
|
||||
admin: {
|
||||
placeholder: {
|
||||
en: 'en placeholder',
|
||||
es: 'es placeholder',
|
||||
},
|
||||
description: {
|
||||
en: 'en description',
|
||||
es: 'es description',
|
||||
},
|
||||
placeholder: {
|
||||
en: 'en placeholder',
|
||||
es: 'es placeholder',
|
||||
},
|
||||
},
|
||||
label: {
|
||||
en: 'Text en',
|
||||
es: 'Text es',
|
||||
},
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'defaultFunction',
|
||||
type: 'text',
|
||||
defaultValue: () => defaultText,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'defaultAsync',
|
||||
type: 'text',
|
||||
defaultValue: async (): Promise<string> => {
|
||||
return new Promise((resolve) =>
|
||||
setTimeout(() => {
|
||||
@@ -53,25 +53,25 @@ const TextFields: CollectionConfig = {
|
||||
}, 1),
|
||||
)
|
||||
},
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
label: 'Override the 40k text length default',
|
||||
name: 'overrideLength',
|
||||
type: 'text',
|
||||
label: 'Override the 40k text length default',
|
||||
maxLength: 50000,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'fieldWithDefaultValue',
|
||||
type: 'text',
|
||||
defaultValue: async () => {
|
||||
const defaultValue = new Promise((resolve) => setTimeout(() => resolve('some-value'), 1000))
|
||||
|
||||
return defaultValue
|
||||
},
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'dependentOnFieldWithDefaultValue',
|
||||
type: 'text',
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
({ data }) => {
|
||||
@@ -79,13 +79,39 @@ const TextFields: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'customLabel',
|
||||
admin: {
|
||||
components: {
|
||||
Label: CustomLabel,
|
||||
},
|
||||
},
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'customError',
|
||||
admin: {
|
||||
components: {
|
||||
Error: CustomError,
|
||||
},
|
||||
},
|
||||
minLength: 3,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'beforeAndAfterInput',
|
||||
admin: {
|
||||
components: {
|
||||
AfterInput: [AfterInput],
|
||||
BeforeInput: [BeforeInput],
|
||||
},
|
||||
},
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const textDoc = {
|
||||
text: 'Seeded text document',
|
||||
localizedText: 'Localized text',
|
||||
slug: textFieldsSlug,
|
||||
}
|
||||
|
||||
export default TextFields
|
||||
|
||||
6
test/fields/collections/Text/shared.ts
Normal file
6
test/fields/collections/Text/shared.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const defaultText = 'default-text'
|
||||
export const textFieldsSlug = 'text-fields'
|
||||
export const textDoc = {
|
||||
text: 'Seeded text document',
|
||||
localizedText: 'Localized text',
|
||||
}
|
||||
@@ -6,13 +6,13 @@ import path from 'path'
|
||||
import payload from '../../packages/payload/src'
|
||||
import { mapAsync } from '../../packages/payload/src/utilities/mapAsync'
|
||||
import wait from '../../packages/payload/src/utilities/wait'
|
||||
import { saveDocAndAssert, saveDocHotkeyAndAssert } from '../helpers'
|
||||
import { exactText, saveDocAndAssert, saveDocHotkeyAndAssert } from '../helpers'
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
|
||||
import { initPayloadE2E } from '../helpers/configHelpers'
|
||||
import { RESTClient } from '../helpers/rest'
|
||||
import { jsonDoc } from './collections/JSON'
|
||||
import { numberDoc } from './collections/Number'
|
||||
import { textDoc } from './collections/Text'
|
||||
import { textDoc } from './collections/Text/shared'
|
||||
import { lexicalE2E } from './lexicalE2E'
|
||||
import { clearAndSeedEverything } from './seed'
|
||||
import {
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
textFieldsSlug,
|
||||
} from './slugs'
|
||||
|
||||
const { afterEach, beforeAll, describe, beforeEach } = test
|
||||
const { afterEach, beforeAll, beforeEach, describe } = test
|
||||
|
||||
let client: RESTClient
|
||||
let page: Page
|
||||
@@ -34,7 +34,7 @@ describe('fields', () => {
|
||||
beforeAll(async ({ browser }) => {
|
||||
const config = await initPayloadE2E(__dirname)
|
||||
serverURL = config.serverURL
|
||||
client = new RESTClient(null, { serverURL, defaultSlug: 'users' })
|
||||
client = new RESTClient(null, { defaultSlug: 'users', serverURL })
|
||||
await client.login()
|
||||
|
||||
const context = await browser.newContext()
|
||||
@@ -43,13 +43,13 @@ describe('fields', () => {
|
||||
beforeEach(async () => {
|
||||
await clearAndSeedEverything(payload)
|
||||
await client.logout()
|
||||
client = new RESTClient(null, { serverURL, defaultSlug: 'users' })
|
||||
client = new RESTClient(null, { defaultSlug: 'users', serverURL })
|
||||
await client.login()
|
||||
})
|
||||
describe('text', () => {
|
||||
let url: AdminUrlUtil
|
||||
beforeAll(() => {
|
||||
url = new AdminUrlUtil(serverURL, 'text-fields')
|
||||
url = new AdminUrlUtil(serverURL, textFieldsSlug)
|
||||
})
|
||||
|
||||
test('should display field in list view', async () => {
|
||||
@@ -80,6 +80,40 @@ describe('fields', () => {
|
||||
const description = page.locator('.field-description-i18nText')
|
||||
await expect(description).toHaveText('en description')
|
||||
})
|
||||
|
||||
test('should render custom label', async () => {
|
||||
await page.goto(url.create)
|
||||
const label = page.locator('label.custom-label[for="field-customLabel"]')
|
||||
await expect(label).toHaveText('#label')
|
||||
})
|
||||
|
||||
test('should render custom error', async () => {
|
||||
await page.goto(url.create)
|
||||
const input = page.locator('input[id="field-customError"]')
|
||||
await input.fill('ab')
|
||||
await expect(input).toHaveValue('ab')
|
||||
const error = page.locator('.custom-error:near(input[id="field-customError"])')
|
||||
const submit = page.locator('button[type="button"][id="action-save"]')
|
||||
await submit.click()
|
||||
await expect(error).toHaveText('#custom-error')
|
||||
})
|
||||
|
||||
test('should render BeforeInput and AfterInput', async () => {
|
||||
await page.goto(url.create)
|
||||
const input = page.locator('input[id="field-beforeAndAfterInput"]')
|
||||
|
||||
const prevSibling = await input.evaluateHandle((el) => {
|
||||
return el.previousElementSibling
|
||||
})
|
||||
const prevSiblingText = await page.evaluate((el) => el.textContent, prevSibling)
|
||||
await expect(prevSiblingText).toEqual('#before-input')
|
||||
|
||||
const nextSibling = await input.evaluateHandle((el) => {
|
||||
return el.nextElementSibling
|
||||
})
|
||||
const nextSiblingText = await page.evaluate((el) => el.textContent, nextSibling)
|
||||
await expect(nextSiblingText).toEqual('#after-input')
|
||||
})
|
||||
})
|
||||
|
||||
describe('number', () => {
|
||||
@@ -166,11 +200,11 @@ describe('fields', () => {
|
||||
await payload.create({
|
||||
collection: 'indexed-fields',
|
||||
data: {
|
||||
text: 'text',
|
||||
uniqueText,
|
||||
group: {
|
||||
unique: uniqueText,
|
||||
},
|
||||
text: 'text',
|
||||
uniqueText,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -286,17 +320,17 @@ describe('fields', () => {
|
||||
filledGroupPoint = await payload.create({
|
||||
collection: pointFieldsSlug,
|
||||
data: {
|
||||
point: [5, 5],
|
||||
localized: [4, 2],
|
||||
group: { point: [4, 2] },
|
||||
localized: [4, 2],
|
||||
point: [5, 5],
|
||||
},
|
||||
})
|
||||
emptyGroupPoint = await payload.create({
|
||||
collection: pointFieldsSlug,
|
||||
data: {
|
||||
point: [5, 5],
|
||||
localized: [3, -2],
|
||||
group: {},
|
||||
localized: [3, -2],
|
||||
point: [5, 5],
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -1139,8 +1173,8 @@ describe('fields', () => {
|
||||
describe('EST', () => {
|
||||
test.use({
|
||||
geolocation: {
|
||||
longitude: -83.0458,
|
||||
latitude: 42.3314,
|
||||
longitude: -83.0458,
|
||||
},
|
||||
timezoneId: 'America/Detroit',
|
||||
})
|
||||
@@ -1160,7 +1194,7 @@ describe('fields', () => {
|
||||
const id = routeSegments.pop()
|
||||
|
||||
// fetch the doc (need the date string from the DB)
|
||||
const { doc } = await client.findByID({ id, slug: 'date-fields', auth: true })
|
||||
const { doc } = await client.findByID({ id, auth: true, slug: 'date-fields' })
|
||||
|
||||
expect(doc.default).toEqual('2023-02-07T12:00:00.000Z')
|
||||
})
|
||||
@@ -1169,8 +1203,8 @@ describe('fields', () => {
|
||||
describe('PST', () => {
|
||||
test.use({
|
||||
geolocation: {
|
||||
longitude: -122.419416,
|
||||
latitude: 37.774929,
|
||||
longitude: -122.419416,
|
||||
},
|
||||
timezoneId: 'America/Los_Angeles',
|
||||
})
|
||||
@@ -1191,7 +1225,7 @@ describe('fields', () => {
|
||||
const id = routeSegments.pop()
|
||||
|
||||
// fetch the doc (need the date string from the DB)
|
||||
const { doc } = await client.findByID({ id, slug: 'date-fields', auth: true })
|
||||
const { doc } = await client.findByID({ id, auth: true, slug: 'date-fields' })
|
||||
|
||||
expect(doc.default).toEqual('2023-02-07T12:00:00.000Z')
|
||||
})
|
||||
@@ -1200,8 +1234,8 @@ describe('fields', () => {
|
||||
describe('ST', () => {
|
||||
test.use({
|
||||
geolocation: {
|
||||
longitude: -171.857,
|
||||
latitude: -14.5994,
|
||||
longitude: -171.857,
|
||||
},
|
||||
timezoneId: 'Pacific/Apia',
|
||||
})
|
||||
@@ -1222,7 +1256,7 @@ describe('fields', () => {
|
||||
const id = routeSegments.pop()
|
||||
|
||||
// fetch the doc (need the date string from the DB)
|
||||
const { doc } = await client.findByID({ id, slug: 'date-fields', auth: true })
|
||||
const { doc } = await client.findByID({ id, auth: true, slug: 'date-fields' })
|
||||
|
||||
expect(doc.default).toEqual('2023-02-07T12:00:00.000Z')
|
||||
})
|
||||
@@ -1245,7 +1279,7 @@ describe('fields', () => {
|
||||
})
|
||||
const relationshipIDs = allRelationshipDocs.docs.map((doc) => doc.id)
|
||||
await mapAsync(relationshipIDs, async (id) => {
|
||||
await payload.delete({ collection: relationshipFieldsSlug, id })
|
||||
await payload.delete({ id, collection: relationshipFieldsSlug })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1354,6 +1388,41 @@ describe('fields', () => {
|
||||
await expect(field.locator('.rs__placeholder')).toBeVisible()
|
||||
})
|
||||
|
||||
test('should display `hasMany` polymorphic relationships', async () => {
|
||||
await page.goto(url.create)
|
||||
const field = page.locator('#field-relationHasManyPolymorphic')
|
||||
await field.click()
|
||||
|
||||
await page
|
||||
.locator('.rs__option', {
|
||||
hasText: exactText('Seeded text document'),
|
||||
})
|
||||
.click()
|
||||
|
||||
await expect(
|
||||
page
|
||||
.locator('#field-relationHasManyPolymorphic .relationship--multi-value-label__text', {
|
||||
hasText: exactText('Seeded text document'),
|
||||
})
|
||||
.first(),
|
||||
).toBeVisible()
|
||||
|
||||
// await fill the required fields then save the document and check again
|
||||
await page.locator('#field-relationship').click()
|
||||
await page.locator('#field-relationship .rs__option:has-text("Seeded text document")').click()
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
const valueAfterSave = page.locator('#field-relationHasManyPolymorphic .multi-value').first()
|
||||
|
||||
await expect(
|
||||
valueAfterSave
|
||||
.locator('.relationship--multi-value-label__text', {
|
||||
hasText: exactText('Seeded text document'),
|
||||
})
|
||||
.first(),
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('should populate relationship dynamic default value', async () => {
|
||||
await page.goto(url.create)
|
||||
await expect(
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
namedTabDefaultValue,
|
||||
namedTabText,
|
||||
} from './collections/Tabs/constants'
|
||||
import { defaultText } from './collections/Text'
|
||||
import { defaultText } from './collections/Text/shared'
|
||||
import { clearAndSeedEverything } from './seed'
|
||||
import { arrayFieldsSlug, groupFieldsSlug, relationshipFieldsSlug, tabsFieldsSlug } from './slugs'
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import { radiosDoc } from './collections/Radio'
|
||||
import { richTextBulletsDoc, richTextDoc } from './collections/RichText/data'
|
||||
import { selectsDoc } from './collections/Select'
|
||||
import { tabsDoc } from './collections/Tabs'
|
||||
import { textDoc } from './collections/Text'
|
||||
import { textDoc } from './collections/Text/shared'
|
||||
import { uploadsDoc } from './collections/Upload'
|
||||
import {
|
||||
blockFieldsSlug,
|
||||
@@ -45,11 +45,8 @@ import {
|
||||
|
||||
export async function clearAndSeedEverything(_payload: Payload) {
|
||||
return await seedDB({
|
||||
snapshotKey: 'fieldsTest',
|
||||
shouldResetDB: true,
|
||||
collectionSlugs,
|
||||
_payload,
|
||||
uploadsDir: path.resolve(__dirname, './collections/Upload/uploads'),
|
||||
collectionSlugs,
|
||||
seedFunction: async (_payload) => {
|
||||
const jpgPath = path.resolve(__dirname, './collections/Upload/payload.jpg')
|
||||
const pngPath = path.resolve(__dirname, './uploads/payload.png')
|
||||
@@ -143,5 +140,8 @@ export async function clearAndSeedEverything(_payload: Payload) {
|
||||
_payload.create({ collection: numberFieldsSlug, data: numberDoc }),
|
||||
])
|
||||
},
|
||||
shouldResetDB: true,
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: path.resolve(__dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user