Skip to content

Commit 6cdf141

Browse files
authored
feat: prevent create new for joins (#8929)
### What? Adds a way to prevent creating new documents from the admin UI in a join field. ### Why? There are two reasons: 1. You want to disable this any time as a feature of your admin user experience 2. When creating a new document it is not yet possible to create the relationship, preventing create is necessary for the workflow to make sense. ### How? join field has a new admin property called `allowCreate`, can be set to false. By default the UI will never allow create when the current document being edited does not yet have an `id`. Fixes # #8892 ### Before Even though the document doesn't have an ID yet, the create buttons are shown which doesn't actually work. ![image](https://github.com/user-attachments/assets/152abed4-a174-498b-835c-aa4779c46834) ### After Initial document creation: ![Screenshot 2024-10-29 125132](https://github.com/user-attachments/assets/f33b1532-5b72-4c94-967d-bda618dadd34) Prevented using `allowCreate: false` ![Screenshot 2024-10-29 130409](https://github.com/user-attachments/assets/69c3f601-fab3-4f5a-9df5-93fd133682ca)
1 parent 2970442 commit 6cdf141

File tree

4 files changed

+34
-19
lines changed

4 files changed

+34
-19
lines changed

docs/fields/join.mdx

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -121,23 +121,33 @@ powerful Admin UI.
121121

122122
## Config Options
123123

124-
| Option | Description |
125-
|------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
126-
| **`name`** \* | To be used as the property name when retrieved from the database. [More](/docs/fields/overview#field-names) |
127-
| **`collection`** \* | The `slug`s having the relationship field. |
128-
| **`on`** \* | The name of the relationship or upload field that relates to the collection document. Use dot notation for nested paths, like 'myGroup.relationName'. |
129-
| **`maxDepth`** | Default is 1, Sets a maximum population depth for this field, regardless of the remaining depth when this field is reached. [Max Depth](/docs/getting-started/concepts#field-level-max-depth) |
130-
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
131-
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
132-
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
133-
| **`defaultLimit`** | The number of documents to return. Set to 0 to return all related documents. |
134-
| **`defaultSort`** | The field name used to specify the order the joined documents are returned. |
135-
| **`admin`** | Admin-specific configuration. |
136-
| **`custom`** | Extension point for adding custom data (e.g. for plugins). |
137-
| **`typescriptSchema`** | Override field type generation with providing a JSON schema. |
124+
| Option | Description |
125+
|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
126+
| **`name`** \* | To be used as the property name when retrieved from the database. [More](/docs/fields/overview#field-names) |
127+
| **`collection`** \* | The `slug`s having the relationship field. |
128+
| **`on`** \* | The name of the relationship or upload field that relates to the collection document. Use dot notation for nested paths, like 'myGroup.relationName'. |
129+
| **`maxDepth`** | Default is 1, Sets a maximum population depth for this field, regardless of the remaining depth when this field is reached. [Max Depth](/docs/getting-started/concepts#field-level-max-depth). |
130+
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
131+
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
132+
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
133+
| **`defaultLimit`** | The number of documents to return. Set to 0 to return all related documents. |
134+
| **`defaultSort`** | The field name used to specify the order the joined documents are returned. |
135+
| **`admin`** | Admin-specific configuration. [More details](#admin-config-options). |
136+
| **`custom`** | Extension point for adding custom data (e.g. for plugins). |
137+
| **`typescriptSchema`** | Override field type generation with providing a JSON schema. |
138138

139139
_\* An asterisk denotes that a property is required._
140140

141+
142+
## Admin Config Options
143+
144+
You can control the user experience of the join field using the `admin` config properties. The following options are supported:
145+
146+
| Option | Description |
147+
|------------------------|----------------------------------------------------------------------------------------|
148+
| **`allowCreate`** | Set to `false` to remove the controls for making new related documents from this field. |
149+
| **`components.Label`** | Override the default Label of the Field Component. [More details](#the-label-component). |
150+
141151
## Join Field Data
142152

143153
When a document is returned that for a Join field is populated with related documents. The structure returned is an

packages/payload/src/fields/config/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1442,6 +1442,7 @@ export type JoinField = {
14421442
update?: never
14431443
}
14441444
admin?: {
1445+
allowCreate?: boolean
14451446
components?: {
14461447
Error?: CustomComponent<JoinFieldErrorClientComponent | JoinFieldErrorServerComponent>
14471448
Label?: CustomComponent<JoinFieldLabelClientComponent | JoinFieldLabelServerComponent>
@@ -1477,6 +1478,7 @@ export type JoinField = {
14771478

14781479
export type JoinFieldClient = {
14791480
admin?: {
1481+
allowCreate?: boolean
14801482
components?: {
14811483
Label?: MappedComponent
14821484
} & AdminClient['components']

packages/ui/src/elements/RelationshipTable/index.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { RelationshipTableWrapper } from './TableWrapper.js'
3737
const baseClass = 'relationship-table'
3838

3939
type RelationshipTableComponentProps = {
40+
readonly allowCreate?: boolean
4041
readonly field: JoinFieldClient
4142
readonly filterOptions?: boolean | Where
4243
readonly initialData?: PaginatedDocs
@@ -47,6 +48,7 @@ type RelationshipTableComponentProps = {
4748

4849
export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (props) => {
4950
const {
51+
allowCreate = true,
5052
field,
5153
filterOptions,
5254
initialData: initialDataFromProps,
@@ -202,16 +204,15 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
202204

203205
const preferenceKey = `${relationTo}-list`
204206

205-
const hasCreatePermission = permissions?.collections?.[relationTo]?.create?.permission
207+
const canCreate =
208+
allowCreate !== false && permissions?.collections?.[relationTo]?.create?.permission
206209

207210
return (
208211
<div className={baseClass}>
209212
<div className={`${baseClass}__header`}>
210213
{Label}
211214
<div className={`${baseClass}__actions`}>
212-
{hasCreatePermission && (
213-
<DocumentDrawerToggler>{i18n.t('fields:addNew')}</DocumentDrawerToggler>
214-
)}
215+
{canCreate && <DocumentDrawerToggler>{i18n.t('fields:addNew')}</DocumentDrawerToggler>}
215216
<Pill
216217
aria-controls={`${baseClass}-columns`}
217218
aria-expanded={openColumnSelector}
@@ -233,7 +234,7 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
233234
label: getTranslation(collectionConfig?.labels?.plural, i18n),
234235
})}
235236
</p>
236-
{hasCreatePermission && (
237+
{canCreate && (
237238
<Button onClick={openDrawer}>
238239
{i18n.t('general:createNewLabel', {
239240
label: getTranslation(collectionConfig?.labels?.singular, i18n),

packages/ui/src/fields/Join/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => {
1919
name,
2020
_path: pathFromProps,
2121
admin: {
22+
allowCreate = true,
2223
components: { Label },
2324
},
2425
collection,
@@ -47,6 +48,7 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => {
4748
return (
4849
<div className={[fieldBaseClass, 'join'].filter(Boolean).join(' ')}>
4950
<RelationshipTable
51+
allowCreate={typeof docID !== 'undefined' && allowCreate}
5052
field={field as JoinFieldClient}
5153
filterOptions={filterOptions}
5254
initialData={docID && value ? value : ({ docs: [] } as PaginatedDocs)}

0 commit comments

Comments
 (0)