fix(next): field permissions
This commit is contained in:
@@ -15,6 +15,7 @@ import { getRequestLanguage } from '../../utilities/getRequestLanguage'
|
||||
import { DefaultEditView } from '../../views/Edit/Default'
|
||||
import { DefaultListView } from '../../views/List/Default'
|
||||
import { DefaultCell } from '../../views/List/Default/Cell'
|
||||
import { getPayload } from '../../utilities/getPayload'
|
||||
|
||||
export const metadata = {
|
||||
description: 'Generated by Next.js',
|
||||
@@ -34,7 +35,13 @@ export const RootLayout = async ({
|
||||
const clientConfig = await createClientConfig(config)
|
||||
|
||||
const headers = getHeaders()
|
||||
const cookies = parseCookies(headers)
|
||||
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
|
||||
const { cookies, user, permissions } = await auth({
|
||||
payload,
|
||||
headers,
|
||||
})
|
||||
|
||||
const lang =
|
||||
getRequestLanguage({
|
||||
@@ -56,6 +63,7 @@ export const RootLayout = async ({
|
||||
DefaultEditView,
|
||||
DefaultListView,
|
||||
config,
|
||||
permissions: permissions,
|
||||
})
|
||||
|
||||
return (
|
||||
|
||||
@@ -10,5 +10,10 @@ export const CreateFirstUserFields: React.FC<{
|
||||
|
||||
const fieldMap = getFieldMap({ collectionSlug: userSlug })
|
||||
|
||||
return <RenderFields fieldMap={[...(fieldMap || []), ...(createFirstUserFieldMap || [])]} />
|
||||
return (
|
||||
<RenderFields
|
||||
fieldMap={[...(fieldMap || []), ...(createFirstUserFieldMap || [])]}
|
||||
operation="create"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -48,10 +48,8 @@ export const CreateFirstUser: React.FC<AdminViewProps> = async ({ initPageResult
|
||||
|
||||
const createFirstUserFieldMap = mapFields({
|
||||
fieldSchema: fields,
|
||||
operation: 'create',
|
||||
config,
|
||||
parentPath: userSlug,
|
||||
permissions: {},
|
||||
})
|
||||
|
||||
const formState = await buildStateFromSchema({
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import React, { Fragment, useEffect, useState } from 'react'
|
||||
|
||||
import './index.scss'
|
||||
import { Permissions } from 'payload/auth'
|
||||
|
||||
const baseClass = 'dashboard'
|
||||
|
||||
@@ -22,7 +23,8 @@ export const DefaultDashboardClient: React.FC<{
|
||||
Link: React.ComponentType
|
||||
visibleCollections: string[]
|
||||
visibleGlobals: string[]
|
||||
}> = ({ Link, visibleCollections, visibleGlobals }) => {
|
||||
permissions: Permissions
|
||||
}> = ({ Link, visibleCollections, visibleGlobals, permissions }) => {
|
||||
const config = useConfig()
|
||||
|
||||
const {
|
||||
@@ -31,7 +33,7 @@ export const DefaultDashboardClient: React.FC<{
|
||||
routes: { admin },
|
||||
} = config
|
||||
|
||||
const { permissions, user } = useAuth()
|
||||
const { user } = useAuth()
|
||||
|
||||
const { i18n, t } = useTranslation()
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import React from 'react'
|
||||
|
||||
import { DefaultDashboardClient } from './index.client'
|
||||
import './index.scss'
|
||||
import { Permissions } from 'payload/auth'
|
||||
|
||||
const baseClass = 'dashboard'
|
||||
|
||||
@@ -13,6 +14,7 @@ export type DashboardProps = {
|
||||
config: SanitizedConfig
|
||||
visibleCollections: string[]
|
||||
visibleGlobals: string[]
|
||||
permissions: Permissions
|
||||
}
|
||||
|
||||
export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
|
||||
@@ -25,6 +27,7 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
|
||||
},
|
||||
visibleCollections,
|
||||
visibleGlobals,
|
||||
permissions,
|
||||
} = props
|
||||
|
||||
return (
|
||||
@@ -38,6 +41,7 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
|
||||
Link={Link}
|
||||
visibleCollections={visibleCollections}
|
||||
visibleGlobals={visibleGlobals}
|
||||
permissions={permissions}
|
||||
/>
|
||||
{Array.isArray(afterDashboard) &&
|
||||
afterDashboard.map((Component, i) => <Component key={i} />)}
|
||||
|
||||
@@ -43,6 +43,7 @@ export const Dashboard: React.FC<AdminViewProps> = ({
|
||||
config,
|
||||
visibleCollections,
|
||||
visibleGlobals,
|
||||
permissions,
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -158,17 +158,6 @@ export const DefaultEditView: React.FC = () => {
|
||||
}`}
|
||||
type="withoutNav"
|
||||
/>
|
||||
{/* <Meta
|
||||
description={`${isEditing ? t('general:editing') : t('general:creating')} - ${getTranslation(
|
||||
collection.labels.singular,
|
||||
i18n,
|
||||
)}`}
|
||||
keywords={`${getTranslation(collection.labels.singular, i18n)}, Payload, CMS`}
|
||||
title={`${isEditing ? t('general:editing') : t('general:creating')} - ${getTranslation(
|
||||
collection.labels.singular,
|
||||
i18n,
|
||||
)}`}
|
||||
/> */}
|
||||
{BeforeDocument}
|
||||
{preventLeaveWithoutSaving && <LeaveWithoutSaving />}
|
||||
<SetStepNav
|
||||
|
||||
@@ -16,9 +16,11 @@ import { useCallback } from 'react'
|
||||
export const EditViewClient: React.FC<EditViewProps> = () => {
|
||||
const { id, collectionSlug, getDocPermissions, getVersions, globalSlug, setDocumentInfo } =
|
||||
useDocumentInfo()
|
||||
|
||||
const {
|
||||
routes: { admin: adminRoute },
|
||||
} = useConfig()
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const { getComponentMap } = useComponentMap()
|
||||
|
||||
@@ -82,7 +82,6 @@ export const getGenerateComponentMap =
|
||||
const mappedFields = mapFields({
|
||||
config,
|
||||
fieldSchema: sanitizedFields,
|
||||
operation: 'update',
|
||||
permissions: {},
|
||||
readOnly: false,
|
||||
})
|
||||
|
||||
@@ -70,7 +70,7 @@ export const getGenerateComponentMap =
|
||||
switch (element.name) {
|
||||
case 'link': {
|
||||
const linkFields = sanitizeFields({
|
||||
config: config,
|
||||
config,
|
||||
fields: transformExtraFields(args.admin?.link?.fields, config, i18n),
|
||||
validRelationships,
|
||||
})
|
||||
@@ -78,7 +78,6 @@ export const getGenerateComponentMap =
|
||||
const mappedFields = mapFields({
|
||||
config,
|
||||
fieldSchema: linkFields,
|
||||
operation: 'update',
|
||||
permissions: {},
|
||||
readOnly: false,
|
||||
})
|
||||
@@ -110,7 +109,6 @@ export const getGenerateComponentMap =
|
||||
const mappedFields = mapFields({
|
||||
config,
|
||||
fieldSchema: uploadFields,
|
||||
operation: 'update',
|
||||
permissions: {},
|
||||
readOnly: false,
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import type { Description } from 'payload/types'
|
||||
import type { Description, Operation } from 'payload/types'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
@import '../../scss/styles.scss';
|
||||
|
||||
.field-description {
|
||||
display: flex;
|
||||
color: var(--theme-elevation-400);
|
||||
margin-top: calc(var(--base) / 4);
|
||||
|
||||
&--margin-bottom {
|
||||
margin-top: 0;
|
||||
margin-bottom: calc(var(--base) / 2);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ type FieldPathContextType = {
|
||||
path: string
|
||||
schemaPath: string
|
||||
}
|
||||
|
||||
const FieldPathContext = React.createContext<FieldPathContextType>({
|
||||
path: '',
|
||||
schemaPath: '',
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
export type Props = {
|
||||
className?: string
|
||||
description?: string
|
||||
marginPlacement?: 'bottom' | 'top'
|
||||
value?: unknown
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
|
||||
import { Banner } from '../../elements/Banner'
|
||||
@@ -13,6 +14,7 @@ type NullifyLocaleFieldProps = {
|
||||
localized: boolean
|
||||
path: string
|
||||
}
|
||||
|
||||
export const NullifyLocaleField: React.FC<NullifyLocaleFieldProps> = ({
|
||||
fieldValue,
|
||||
localized,
|
||||
@@ -30,8 +32,8 @@ export const NullifyLocaleField: React.FC<NullifyLocaleFieldProps> = ({
|
||||
const useFallback = !checked
|
||||
|
||||
dispatchFields({
|
||||
path,
|
||||
type: 'UPDATE',
|
||||
path,
|
||||
value: useFallback ? null : fieldValue || 0,
|
||||
})
|
||||
setModified(true)
|
||||
|
||||
18
packages/ui/src/forms/ReadOnlyProvider/index.tsx
Normal file
18
packages/ui/src/forms/ReadOnlyProvider/index.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
|
||||
const ReadOnlyContext = React.createContext<boolean | undefined>(undefined)
|
||||
|
||||
export const ReadOnlyProvider: React.FC<{
|
||||
children: React.ReactNode
|
||||
readOnly?: boolean
|
||||
}> = (props) => {
|
||||
const { children, readOnly } = props
|
||||
|
||||
return <ReadOnlyContext.Provider value={readOnly}>{children}</ReadOnlyContext.Provider>
|
||||
}
|
||||
|
||||
export const useReadOnly = () => {
|
||||
const path = React.useContext(ReadOnlyContext)
|
||||
return path
|
||||
}
|
||||
@@ -1,20 +1,45 @@
|
||||
'use client'
|
||||
import type { FieldPermissions } from 'payload/auth'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import { useOperation } from '../../providers/OperationProvider'
|
||||
import { FieldPathProvider, useFieldPath } from '../FieldPathProvider'
|
||||
import { ReadOnlyProvider, useReadOnly } from '../ReadOnlyProvider'
|
||||
|
||||
export const RenderField: React.FC<{
|
||||
Field: React.ReactNode
|
||||
fieldPermissions: FieldPermissions
|
||||
name?: string
|
||||
readOnly?: boolean
|
||||
}> = (props) => {
|
||||
const { name, Field } = props
|
||||
const { name, Field, fieldPermissions, readOnly: readOnlyFromProps } = props
|
||||
|
||||
const { path: pathFromContext, schemaPath: schemaPathFromContext } = useFieldPath()
|
||||
|
||||
const readOnlyFromContext = useReadOnly()
|
||||
|
||||
const operation = useOperation()
|
||||
|
||||
const path = `${pathFromContext ? `${pathFromContext}.` : ''}${name || ''}`
|
||||
const schemaPath = `${schemaPathFromContext ? `${schemaPathFromContext}.` : ''}${name || ''}`
|
||||
|
||||
// `admin.readOnly` displays the value but prevents the field from being edited
|
||||
let readOnly = readOnlyFromProps
|
||||
|
||||
// if parent field is `readOnly: true`, but this field is `readOnly: false`, the field should still be editable
|
||||
if (readOnlyFromContext && readOnly !== false) readOnly = true
|
||||
|
||||
// if the user does not have access control to begin with, force it to be read-only
|
||||
if (fieldPermissions?.[operation]?.permission === false) {
|
||||
readOnly = true
|
||||
}
|
||||
|
||||
return (
|
||||
<ReadOnlyProvider readOnly={readOnly}>
|
||||
<FieldPathProvider path={path} schemaPath={schemaPath}>
|
||||
{Field}
|
||||
</FieldPathProvider>
|
||||
</ReadOnlyProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -53,8 +53,14 @@ const RenderFields: React.FC<Props> = (props) => {
|
||||
ref={intersectionRef}
|
||||
>
|
||||
{hasRendered &&
|
||||
fieldMap?.map(({ name, Field }, fieldIndex) => (
|
||||
<RenderField Field={Field} key={fieldIndex} name={name} />
|
||||
fieldMap?.map(({ name, Field, fieldPermissions, readOnly }, fieldIndex) => (
|
||||
<RenderField
|
||||
Field={Field}
|
||||
fieldPermissions={fieldPermissions}
|
||||
key={fieldIndex}
|
||||
name={name}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { Operation } from 'payload/types'
|
||||
|
||||
import type { FieldMap } from '../../utilities/buildComponentMap/types'
|
||||
|
||||
export type Props = {
|
||||
@@ -5,4 +7,5 @@ export type Props = {
|
||||
fieldMap: FieldMap
|
||||
forceRender?: boolean
|
||||
margins?: 'small' | false
|
||||
operation?: Operation
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { FieldPermissions } from 'payload/auth'
|
||||
import type { ArrayField, Row, RowLabel as RowLabelType } from 'payload/types'
|
||||
import type { ArrayField, Operation, Row, RowLabel as RowLabelType } from 'payload/types'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import React from 'react'
|
||||
|
||||
@@ -105,10 +105,10 @@ const BlocksField: React.FC<Props> = (props) => {
|
||||
const addRow = useCallback(
|
||||
async (rowIndex: number, blockType: string) => {
|
||||
dispatchFields({
|
||||
type: 'ADD_ROW',
|
||||
blockType,
|
||||
path,
|
||||
rowIndex,
|
||||
type: 'ADD_ROW',
|
||||
})
|
||||
|
||||
setModified(true)
|
||||
@@ -117,12 +117,12 @@ const BlocksField: React.FC<Props> = (props) => {
|
||||
scrollToID(`${path}-row-${rowIndex + 1}`)
|
||||
}, 0)
|
||||
},
|
||||
[path, setModified],
|
||||
[path, setModified, dispatchFields],
|
||||
)
|
||||
|
||||
const duplicateRow = useCallback(
|
||||
(rowIndex: number) => {
|
||||
dispatchFields({ path, rowIndex, type: 'DUPLICATE_ROW' })
|
||||
dispatchFields({ type: 'DUPLICATE_ROW', path, rowIndex })
|
||||
setModified(true)
|
||||
|
||||
setTimeout(() => {
|
||||
@@ -135,9 +135,9 @@ const BlocksField: React.FC<Props> = (props) => {
|
||||
const removeRow = useCallback(
|
||||
(rowIndex: number) => {
|
||||
dispatchFields({
|
||||
type: 'REMOVE_ROW',
|
||||
path,
|
||||
rowIndex,
|
||||
type: 'REMOVE_ROW',
|
||||
})
|
||||
|
||||
setModified(true)
|
||||
@@ -147,7 +147,7 @@ const BlocksField: React.FC<Props> = (props) => {
|
||||
|
||||
const moveRow = useCallback(
|
||||
(moveFromIndex: number, moveToIndex: number) => {
|
||||
dispatchFields({ moveFromIndex, moveToIndex, path, type: 'MOVE_ROW' })
|
||||
dispatchFields({ type: 'MOVE_ROW', moveFromIndex, moveToIndex, path })
|
||||
setModified(true)
|
||||
},
|
||||
[dispatchFields, path, setModified],
|
||||
@@ -155,14 +155,14 @@ const BlocksField: React.FC<Props> = (props) => {
|
||||
|
||||
const toggleCollapseAll = useCallback(
|
||||
(collapsed: boolean) => {
|
||||
dispatchFields({ collapsed, path, setDocFieldPreferences, type: 'SET_ALL_ROWS_COLLAPSED' })
|
||||
dispatchFields({ type: 'SET_ALL_ROWS_COLLAPSED', collapsed, path, setDocFieldPreferences })
|
||||
},
|
||||
[dispatchFields, path, setDocFieldPreferences],
|
||||
)
|
||||
|
||||
const setCollapse = useCallback(
|
||||
(rowID: string, collapsed: boolean) => {
|
||||
dispatchFields({ collapsed, path, rowID, setDocFieldPreferences, type: 'SET_ROW_COLLAPSED' })
|
||||
dispatchFields({ type: 'SET_ROW_COLLAPSED', collapsed, path, rowID, setDocFieldPreferences })
|
||||
},
|
||||
[dispatchFields, path, setDocFieldPreferences],
|
||||
)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { DocumentPreferences } from 'payload/types'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { tabHasName } from 'payload/types'
|
||||
import { toKebabCase } from 'payload/utilities'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import type { Props } from './types'
|
||||
|
||||
import { useConfig } from '../../../providers/Config'
|
||||
import { useLocale } from '../../../providers/Locale'
|
||||
import { useTranslation } from '../../../providers/Translation'
|
||||
import LabelComp from '../../Label'
|
||||
import useField from '../../useField'
|
||||
import { withCondition } from '../../withCondition'
|
||||
@@ -33,10 +32,8 @@ const Text: React.FC<Props> = (props) => {
|
||||
maxRows,
|
||||
minLength,
|
||||
minRows,
|
||||
onKeyDown,
|
||||
path: pathFromProps,
|
||||
placeholder,
|
||||
readOnly,
|
||||
required,
|
||||
rtl,
|
||||
style,
|
||||
@@ -46,8 +43,6 @@ const Text: React.FC<Props> = (props) => {
|
||||
|
||||
const Label = LabelFromProps || <LabelComp label={label} required={required} />
|
||||
|
||||
const { i18n, t } = useTranslation()
|
||||
|
||||
const locale = useLocale()
|
||||
|
||||
const { localization: localizationConfig } = useConfig()
|
||||
@@ -60,7 +55,7 @@ const Text: React.FC<Props> = (props) => {
|
||||
[validate, minLength, maxLength, required],
|
||||
)
|
||||
|
||||
const { path, schemaPath, setValue, showError, value } = useField({
|
||||
const { path, readOnly, setValue, showError, value } = useField({
|
||||
path: pathFromProps || name,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
@@ -31,7 +31,6 @@ const Textarea: React.FC<Props> = (props) => {
|
||||
minLength,
|
||||
path: pathFromProps,
|
||||
placeholder,
|
||||
readOnly,
|
||||
required,
|
||||
rtl,
|
||||
style,
|
||||
@@ -62,7 +61,7 @@ const Textarea: React.FC<Props> = (props) => {
|
||||
[validate, required, maxLength, minLength],
|
||||
)
|
||||
|
||||
const { path, setValue, showError, value } = useField<string>({
|
||||
const { path, readOnly, setValue, showError, value } = useField<string>({
|
||||
path: pathFromProps || name,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { User } from 'payload/auth'
|
||||
import type { FieldPermissions, User } from 'payload/auth'
|
||||
import type { Locale, SanitizedLocalizationConfig } from 'payload/config'
|
||||
import type {
|
||||
ArrayField,
|
||||
@@ -33,6 +33,7 @@ export type FormFieldBase = {
|
||||
className?: string
|
||||
docPreferences?: DocumentPreferences
|
||||
fieldMap?: FieldMap
|
||||
fieldPermissions?: FieldPermissions
|
||||
initialSubfieldState?: FormState
|
||||
label?: string
|
||||
locale?: Locale
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useOperation } from '../../providers/OperationProvider'
|
||||
import { useTranslation } from '../../providers/Translation'
|
||||
import { useFieldPath } from '../FieldPathProvider'
|
||||
import { useForm, useFormFields, useFormProcessing, useFormSubmitted } from '../Form/context'
|
||||
import { useReadOnly } from '../ReadOnlyProvider'
|
||||
|
||||
/**
|
||||
* Get and set the value of a form field.
|
||||
@@ -23,6 +24,8 @@ const useField = <T,>(options: Options): FieldType<T> => {
|
||||
|
||||
const { path: pathFromContext, schemaPath } = useFieldPath()
|
||||
|
||||
const readOnly = useReadOnly()
|
||||
|
||||
const path = options.path || pathFromContext
|
||||
|
||||
const submitted = useFormSubmitted()
|
||||
@@ -83,6 +86,7 @@ const useField = <T,>(options: Options): FieldType<T> => {
|
||||
formSubmitted: submitted,
|
||||
initialValue,
|
||||
path,
|
||||
readOnly: readOnly || false,
|
||||
rows: field?.rows,
|
||||
schemaPath,
|
||||
setValue,
|
||||
@@ -102,6 +106,7 @@ const useField = <T,>(options: Options): FieldType<T> => {
|
||||
initialValue,
|
||||
path,
|
||||
schemaPath,
|
||||
readOnly,
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ export type FieldType<T> = {
|
||||
formSubmitted: boolean
|
||||
initialValue?: T
|
||||
path: string
|
||||
readOnly?: boolean
|
||||
rows?: Row[]
|
||||
schemaPath: string
|
||||
setValue: (val: unknown, disableModifyingForm?: boolean) => void
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import type { Permissions, User } from 'payload/auth'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { useAuth } from '..'
|
||||
|
||||
export const HydrateClientUser: React.FC<{ permissions: Permissions; user: User }> = ({
|
||||
permissions,
|
||||
user,
|
||||
}) => {
|
||||
const { setPermissions, setUser } = useAuth()
|
||||
|
||||
useEffect(() => {
|
||||
setUser(user)
|
||||
setPermissions(permissions)
|
||||
}, [user, permissions, setUser, setPermissions])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -159,6 +159,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
const params = {
|
||||
locale: code,
|
||||
}
|
||||
|
||||
try {
|
||||
const request = await requests.get(`${serverURL}${api}/access?${qs.stringify(params)}`, {
|
||||
headers: {
|
||||
@@ -256,13 +257,6 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
setLastLocationChange(Date.now())
|
||||
}, [pathname])
|
||||
|
||||
// When user changes, get new access
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
refreshPermissions()
|
||||
}
|
||||
}, [i18n, id, api, serverURL, refreshPermissions])
|
||||
|
||||
useEffect(() => {
|
||||
let reminder: ReturnType<typeof setTimeout>
|
||||
const now = Math.round(new Date().getTime() / 1000)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { FieldPermissions } from 'payload/auth'
|
||||
import type { CollectionPermission, GlobalPermission, Permissions } from 'payload/auth'
|
||||
import type { EditViewProps, SanitizedConfig } from 'payload/types'
|
||||
|
||||
import React from 'react'
|
||||
@@ -13,12 +13,7 @@ export const buildComponentMap = (args: {
|
||||
DefaultEditView: React.FC<EditViewProps>
|
||||
DefaultListView: React.FC<EditViewProps>
|
||||
config: SanitizedConfig
|
||||
operation?: 'create' | 'update'
|
||||
permissions?:
|
||||
| {
|
||||
[field: string]: FieldPermissions
|
||||
}
|
||||
| FieldPermissions
|
||||
permissions?: Permissions
|
||||
readOnly?: boolean
|
||||
}): ComponentMap => {
|
||||
const {
|
||||
@@ -26,15 +21,18 @@ export const buildComponentMap = (args: {
|
||||
DefaultEditView,
|
||||
DefaultListView,
|
||||
config,
|
||||
operation = 'update',
|
||||
permissions,
|
||||
readOnly: readOnlyOverride,
|
||||
} = args
|
||||
|
||||
let entityPermissions: CollectionPermission | GlobalPermission
|
||||
|
||||
// Collections
|
||||
const collections = config.collections.reduce((acc, collectionConfig) => {
|
||||
const { slug, fields } = collectionConfig
|
||||
|
||||
entityPermissions = permissions.collections[collectionConfig.slug]
|
||||
|
||||
const editViewFromConfig = collectionConfig?.admin?.components?.views?.Edit
|
||||
const listViewFromConfig = collectionConfig?.admin?.components?.views?.List
|
||||
|
||||
@@ -110,8 +108,7 @@ export const buildComponentMap = (args: {
|
||||
DefaultCell,
|
||||
config,
|
||||
fieldSchema: fields,
|
||||
operation,
|
||||
permissions,
|
||||
permissions: entityPermissions.fields,
|
||||
readOnly: readOnlyOverride,
|
||||
}),
|
||||
}
|
||||
@@ -126,6 +123,8 @@ export const buildComponentMap = (args: {
|
||||
const globals = config.globals.reduce((acc, globalConfig) => {
|
||||
const { slug, fields } = globalConfig
|
||||
|
||||
entityPermissions = permissions.globals[globalConfig.slug]
|
||||
|
||||
const editViewFromConfig = globalConfig?.admin?.components?.views?.Edit
|
||||
|
||||
const CustomEditView =
|
||||
@@ -150,8 +149,7 @@ export const buildComponentMap = (args: {
|
||||
DefaultCell,
|
||||
config,
|
||||
fieldSchema: fields,
|
||||
operation,
|
||||
permissions,
|
||||
permissions: entityPermissions.fields,
|
||||
readOnly: readOnlyOverride,
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { FieldPermissions } from 'payload/auth'
|
||||
import type { CollectionPermission, FieldPermissions, GlobalPermission } from 'payload/auth'
|
||||
import type { CellProps, Field, FieldWithPath, LabelProps, SanitizedConfig } from 'payload/types'
|
||||
|
||||
import { fieldAffectsData, fieldIsPresentationalOnly } from 'payload/types'
|
||||
@@ -21,13 +21,8 @@ export const mapFields = (args: {
|
||||
config: SanitizedConfig
|
||||
fieldSchema: FieldWithPath[]
|
||||
filter?: (field: Field) => boolean
|
||||
operation?: 'create' | 'update'
|
||||
parentPath?: string
|
||||
permissions?:
|
||||
| {
|
||||
[field: string]: FieldPermissions
|
||||
}
|
||||
| FieldPermissions
|
||||
permissions?: CollectionPermission['fields'] | GlobalPermission['fields']
|
||||
readOnly?: boolean
|
||||
}): FieldMap => {
|
||||
const {
|
||||
@@ -35,7 +30,6 @@ export const mapFields = (args: {
|
||||
config,
|
||||
fieldSchema,
|
||||
filter,
|
||||
operation = 'update',
|
||||
parentPath,
|
||||
permissions,
|
||||
readOnly: readOnlyOverride,
|
||||
@@ -57,26 +51,14 @@ export const mapFields = (args: {
|
||||
field.path || (isFieldAffectingData && 'name' in field ? field.name : '')
|
||||
}`
|
||||
|
||||
const fieldPermissions = isFieldAffectingData ? permissions?.[field.name] : permissions
|
||||
const fieldPermissions = isFieldAffectingData ? permissions?.[field.name] : undefined
|
||||
|
||||
// if the user cannot read the field, then filter it out
|
||||
// this is different from `admin.readOnly` which is executed on the client based on `operation`
|
||||
if (fieldPermissions?.read?.permission === false) {
|
||||
return acc
|
||||
}
|
||||
|
||||
// readOnly from field config
|
||||
let readOnly = field.admin && 'readOnly' in field.admin ? field.admin.readOnly : undefined
|
||||
|
||||
// if parent field is readOnly
|
||||
// but this field is `readOnly: false`
|
||||
// the field should be editable
|
||||
if (readOnlyOverride && readOnly !== false) readOnly = true
|
||||
|
||||
// unless the user does not pass access control
|
||||
if (fieldPermissions?.[operation]?.permission === false) {
|
||||
readOnly = true
|
||||
}
|
||||
|
||||
const labelProps: LabelProps = {
|
||||
htmlFor: 'TODO',
|
||||
// TODO: fix types
|
||||
@@ -103,7 +85,6 @@ export const mapFields = (args: {
|
||||
config,
|
||||
fieldSchema: field.fields,
|
||||
filter,
|
||||
operation,
|
||||
parentPath: path,
|
||||
permissions,
|
||||
readOnly: readOnlyOverride,
|
||||
@@ -120,7 +101,6 @@ export const mapFields = (args: {
|
||||
config,
|
||||
fieldSchema: tab.fields,
|
||||
filter,
|
||||
operation,
|
||||
parentPath: path,
|
||||
permissions,
|
||||
readOnly: readOnlyOverride,
|
||||
@@ -146,7 +126,6 @@ export const mapFields = (args: {
|
||||
config,
|
||||
fieldSchema: block.fields,
|
||||
filter,
|
||||
operation,
|
||||
parentPath: `${path}.${block.slug}`,
|
||||
permissions,
|
||||
readOnly: readOnlyOverride,
|
||||
@@ -232,11 +211,14 @@ export const mapFields = (args: {
|
||||
// TODO: fix types
|
||||
// label: 'label' in field ? field.label : undefined,
|
||||
blocks,
|
||||
fieldPermissions,
|
||||
hasMany: 'hasMany' in field ? field.hasMany : undefined,
|
||||
max: 'max' in field ? field.max : undefined,
|
||||
maxRows: 'maxRows' in field ? field.maxRows : undefined,
|
||||
min: 'min' in field ? field.min : undefined,
|
||||
options: 'options' in field ? field.options : undefined,
|
||||
readOnly:
|
||||
'admin' in field && 'readOnly' in field.admin ? field.admin.readOnly : undefined,
|
||||
relationTo: 'relationTo' in field ? field.relationTo : undefined,
|
||||
richTextComponentMap: undefined,
|
||||
step: 'admin' in field && 'step' in field.admin ? field.admin.step : undefined,
|
||||
@@ -353,7 +335,8 @@ export const mapFields = (args: {
|
||||
labels: 'labels' in field ? field.labels : undefined,
|
||||
localized: 'localized' in field ? field.localized : false,
|
||||
options: 'options' in field ? field.options : undefined,
|
||||
readOnly,
|
||||
readOnly:
|
||||
'admin' in field && 'readOnly' in field.admin ? field.admin.readOnly : undefined,
|
||||
relationTo: 'relationTo' in field ? field.relationTo : undefined,
|
||||
subfields: nestedFieldMap,
|
||||
tabs,
|
||||
@@ -379,7 +362,7 @@ export const mapFields = (args: {
|
||||
Field: <HiddenInput name="id" />,
|
||||
Heading: <SortColumn label="ID" name="id" />,
|
||||
fieldIsPresentational: false,
|
||||
fieldPermissions: {} as FieldPermissions, // TODO: wire this up
|
||||
fieldPermissions: {} as FieldPermissions,
|
||||
isFieldAffectingData: true,
|
||||
isSidebar: false,
|
||||
label: 'ID',
|
||||
|
||||
@@ -53,6 +53,9 @@ export type MappedField = {
|
||||
* On `select` fields only
|
||||
*/
|
||||
options?: Option[]
|
||||
/**
|
||||
* This is the `admin.readOnly` value from the field's config
|
||||
*/
|
||||
readOnly: boolean
|
||||
/**
|
||||
* On `relationship` fields only
|
||||
|
||||
Reference in New Issue
Block a user