chore(next): ssr field validations (#4700)

This commit is contained in:
Jacob Fletcher
2024-01-05 12:15:14 -05:00
committed by GitHub
parent bd6a3a633d
commit e4e5cab60f
57 changed files with 472 additions and 453 deletions

View File

@@ -4,7 +4,7 @@ import { DocumentLayout } from '@payloadcms/next/layouts/Document'
import configPromise from 'payload-config' import configPromise from 'payload-config'
export default async ({ children, params }: { children: React.ReactNode; params }) => ( export default async ({ children, params }: { children: React.ReactNode; params }) => (
<DocumentLayout config={configPromise} collectionSlug={params.collection} id={params.id}> <DocumentLayout config={configPromise} collectionSlug={params.collection}>
{children} {children}
</DocumentLayout> </DocumentLayout>
) )

View File

@@ -15,13 +15,11 @@ export const DocumentLayout = async ({
config: configPromise, config: configPromise,
collectionSlug, collectionSlug,
globalSlug, globalSlug,
id,
}: { }: {
children: React.ReactNode children: React.ReactNode
config: Promise<SanitizedConfig> config: Promise<SanitizedConfig>
collectionSlug?: string collectionSlug?: string
globalSlug?: string globalSlug?: string
id?: string
}) => { }) => {
const { user, permissions, config } = await initPage(configPromise) const { user, permissions, config } = await initPage(configPromise)
@@ -36,14 +34,9 @@ export const DocumentLayout = async ({
return ( return (
<Fragment> <Fragment>
<DocumentHeader <DocumentHeader
// apiURL={apiURL}
config={config} config={config}
collectionConfig={collectionConfig} collectionConfig={collectionConfig}
globalConfig={globalConfig} globalConfig={globalConfig}
// customHeader={customHeader}
// data={data}
id={id}
// isEditing={isEditing}
/> />
{children} {children}
</Fragment> </Fragment>

View File

@@ -107,7 +107,7 @@ export const Account = async ({
return ( return (
<Fragment> <Fragment>
<HydrateClientUser user={user} /> <HydrateClientUser user={user} permissions={permissions} />
<RenderCustomComponent <RenderCustomComponent
CustomComponent={ CustomComponent={
typeof CustomAccountComponent === 'function' ? CustomAccountComponent : undefined typeof CustomAccountComponent === 'function' ? CustomAccountComponent : undefined

View File

@@ -13,9 +13,11 @@ import {
FormQueryParamsProvider, FormQueryParamsProvider,
QueryParamTypes, QueryParamTypes,
HydrateClientUser, HydrateClientUser,
DocumentInfoProvider,
} from '@payloadcms/ui' } from '@payloadcms/ui'
import queryString from 'qs' import queryString from 'qs'
import { notFound } from 'next/navigation' import { notFound } from 'next/navigation'
import { TFunction } from 'i18next'
export const CollectionEdit = async ({ export const CollectionEdit = async ({
collectionSlug, collectionSlug,
@@ -99,7 +101,7 @@ export const CollectionEdit = async ({
locale, locale,
operation: isEditing ? 'update' : 'create', operation: isEditing ? 'update' : 'create',
preferences, preferences,
// t, t: ((key: string) => key) as TFunction, // TODO: i18n
user, user,
}) })
@@ -136,16 +138,24 @@ export const CollectionEdit = async ({
return ( return (
<Fragment> <Fragment>
<HydrateClientUser user={user} /> <HydrateClientUser user={user} permissions={permissions} />
<EditDepthProvider depth={1}> <DocumentInfoProvider
<FormQueryParamsProvider formQueryParams={formQueryParams}> collectionSlug={collectionConfig.slug}
<RenderCustomComponent id={id}
CustomComponent={typeof CustomEdit === 'function' ? CustomEdit : undefined} key={`${collectionSlug}-${locale}`}
DefaultComponent={DefaultEditView} versionsEnabled={Boolean(collectionConfig.versions)}
componentProps={componentProps} draftsEnabled={Boolean(collectionConfig.versions?.drafts)}
/> >
</FormQueryParamsProvider> <EditDepthProvider depth={1}>
</EditDepthProvider> <FormQueryParamsProvider formQueryParams={formQueryParams}>
<RenderCustomComponent
CustomComponent={typeof CustomEdit === 'function' ? CustomEdit : undefined}
DefaultComponent={DefaultEditView}
componentProps={componentProps}
/>
</FormQueryParamsProvider>
</EditDepthProvider>
</DocumentInfoProvider>
</Fragment> </Fragment>
) )
} }

View File

@@ -67,7 +67,7 @@ export const CollectionList = async ({
return ( return (
<Fragment> <Fragment>
<HydrateClientUser user={user} /> <HydrateClientUser user={user} permissions={permissions} />
<RenderCustomComponent <RenderCustomComponent
CustomComponent={ListToRender} CustomComponent={ListToRender}
DefaultComponent={DefaultList} DefaultComponent={DefaultList}

View File

@@ -11,13 +11,13 @@ export const Dashboard = async ({
config: Promise<SanitizedConfig> config: Promise<SanitizedConfig>
searchParams: { [key: string]: string | string[] | undefined } searchParams: { [key: string]: string | string[] | undefined }
}) => { }) => {
const { config, user } = await initPage(configPromise, true) const { config, user, permissions } = await initPage(configPromise, true)
const CustomDashboardComponent = config.admin.components?.views?.Dashboard const CustomDashboardComponent = config.admin.components?.views?.Dashboard
return ( return (
<Fragment> <Fragment>
<HydrateClientUser user={user} /> <HydrateClientUser user={user} permissions={permissions} />
<RenderCustomComponent <RenderCustomComponent
CustomComponent={ CustomComponent={
typeof CustomDashboardComponent === 'function' ? CustomDashboardComponent : undefined typeof CustomDashboardComponent === 'function' ? CustomDashboardComponent : undefined

View File

@@ -13,6 +13,7 @@ import {
FormQueryParamsProvider, FormQueryParamsProvider,
QueryParamTypes, QueryParamTypes,
HydrateClientUser, HydrateClientUser,
DocumentInfoProvider,
} from '@payloadcms/ui' } from '@payloadcms/ui'
import { notFound } from 'next/navigation' import { notFound } from 'next/navigation'
import { Metadata } from 'next' import { Metadata } from 'next'
@@ -126,7 +127,7 @@ export const Global = async ({
globalConfig, globalConfig,
data, data,
fieldTypes, fieldTypes,
initialState: state, state,
permissions: globalPermission, permissions: globalPermission,
updatedAt: data?.updatedAt?.toString(), updatedAt: data?.updatedAt?.toString(),
user, user,
@@ -135,16 +136,23 @@ export const Global = async ({
return ( return (
<Fragment> <Fragment>
<HydrateClientUser user={user} /> <HydrateClientUser user={user} permissions={permissions} />
<EditDepthProvider depth={1}> <DocumentInfoProvider
<FormQueryParamsProvider formQueryParams={formQueryParams}> collectionSlug={globalConfig.slug}
<RenderCustomComponent key={`${globalSlug}-${locale}`}
CustomComponent={typeof CustomEdit === 'function' ? CustomEdit : undefined} versionsEnabled={Boolean(globalConfig.versions)}
DefaultComponent={DefaultGlobalView} draftsEnabled={Boolean(globalConfig.versions?.drafts)}
componentProps={componentProps} >
/> <EditDepthProvider depth={1}>
</FormQueryParamsProvider> <FormQueryParamsProvider formQueryParams={formQueryParams}>
</EditDepthProvider> <RenderCustomComponent
CustomComponent={typeof CustomEdit === 'function' ? CustomEdit : undefined}
DefaultComponent={DefaultGlobalView}
componentProps={componentProps}
/>
</FormQueryParamsProvider>
</EditDepthProvider>
</DocumentInfoProvider>
</Fragment> </Fragment>
) )
} }

View File

@@ -5,6 +5,7 @@ import { DefaultVersionsView } from './Default'
import { SanitizedConfig } from 'payload/types' import { SanitizedConfig } from 'payload/types'
import { initPage } from '../../utilities/initPage' import { initPage } from '../../utilities/initPage'
import { DefaultVersionsViewProps } from './Default/types' import { DefaultVersionsViewProps } from './Default/types'
import { notFound } from 'next/navigation'
export const VersionsView = async ({ export const VersionsView = async ({
collectionSlug, collectionSlug,
@@ -46,28 +47,40 @@ export const VersionsView = async ({
let versionsData let versionsData
if (collectionSlug) { if (collectionSlug) {
data = await payload.findByID({ try {
collection: collectionSlug, data = await payload.findByID({
id, collection: collectionSlug,
depth: 0, id,
user, depth: 0,
// draft: true, user,
}) // draft: true,
})
} catch (error) {
console.error(error)
}
versionsData = await payload.findVersions({ if (!data) {
collection: collectionSlug, return notFound()
depth: 0, }
user,
page: page ? parseInt(page as string, 10) : undefined, try {
sort: sort as string, versionsData = await payload.findVersions({
// TODO: why won't this work?! collection: collectionSlug,
// throws an `unsupported BSON` error depth: 0,
// where: { user,
// parent: { page: page ? parseInt(page as string, 10) : undefined,
// equals: id, sort: sort as string,
// }, // TODO: why won't this work?!
// }, // throws an `unsupported BSON` error
}) // where: {
// parent: {
// equals: id,
// },
// },
})
} catch (error) {
console.error(error)
}
docURL = `${serverURL}${api}/${slug}/${id}` docURL = `${serverURL}${api}/${slug}/${id}`
// entityLabel = getTranslation(collectionConfig.labels.singular, i18n) // entityLabel = getTranslation(collectionConfig.labels.singular, i18n)
@@ -97,25 +110,41 @@ export const VersionsView = async ({
} }
if (globalSlug) { if (globalSlug) {
data = await payload.findGlobal({ try {
slug: globalSlug, data = await payload.findGlobal({
depth: 0, slug: globalSlug,
user, depth: 0,
// draft: true, user,
}) // draft: true,
})
} catch (error) {
console.error(error)
}
versionsData = await payload.findGlobalVersions({ if (!data) {
slug: globalSlug, return notFound()
depth: 0, }
user,
page: page ? parseInt(page as string, 10) : undefined, try {
sort: sort as string, versionsData = await payload.findGlobalVersions({
where: { slug: globalSlug,
parent: { depth: 0,
equals: id, user,
page: page ? parseInt(page as string, 10) : undefined,
sort: sort as string,
where: {
parent: {
equals: id,
},
}, },
}, })
}) } catch (error) {
console.error(error)
}
if (!versionsData) {
return notFound()
}
docURL = `${serverURL}${api}/globals/${globalSlug}` docURL = `${serverURL}${api}/globals/${globalSlug}`
// entityLabel = getTranslation(globalConfig.label, i18n) // entityLabel = getTranslation(globalConfig.label, i18n)

View File

@@ -2,7 +2,6 @@
import { Modal, useModal } from '@faceless-ui/modal' import { Modal, useModal } from '@faceless-ui/modal'
import React, { useCallback, useState } from 'react' import React, { useCallback, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import { useHistory } from 'react-router-dom'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import type { Props } from './types' import type { Props } from './types'
@@ -11,22 +10,17 @@ import { getTranslation } from 'payload/utilities'
// import { requests } from '../../../api' // import { requests } from '../../../api'
import useTitle from '../../hooks/useTitle' import useTitle from '../../hooks/useTitle'
import { useForm } from '../../forms/Form/context' import { useForm } from '../../forms/Form/context'
import { Minimal as MinimalTemplate } from '../../templates/Minimal' import { MinimalTemplate } from '../../templates/Minimal'
import { useConfig } from '../../providers/Config' import { useConfig } from '../../providers/Config'
import { Button } from '../Button' import { Button } from '../Button'
import * as PopupList from '../Popup/PopupButtonList' import * as PopupList from '../Popup/PopupButtonList'
import './index.scss' import './index.scss'
import { useRouter } from 'next/navigation'
const baseClass = 'delete-document' const baseClass = 'delete-document'
const DeleteDocument: React.FC<Props> = (props) => { const DeleteDocument: React.FC<Props> = (props) => {
const { const { id, buttonId, useAsTitle, collectionSlug, singularLabel, title: titleFromProps } = props
id,
buttonId,
collection: { labels: { singular } = {}, slug } = {},
collection,
title: titleFromProps,
} = props
const { const {
routes: { admin, api }, routes: { admin, api },
@@ -36,9 +30,12 @@ const DeleteDocument: React.FC<Props> = (props) => {
const { setModified } = useForm() const { setModified } = useForm()
const [deleting, setDeleting] = useState(false) const [deleting, setDeleting] = useState(false)
const { toggleModal } = useModal() const { toggleModal } = useModal()
const history = useHistory() const history = useRouter()
const { i18n, t } = useTranslation('general') const { i18n, t } = useTranslation('general')
const title = useTitle({ collection }) const title = useTitle({
useAsTitle,
})
const titleToRender = titleFromProps || title || id const titleToRender = titleFromProps || title || id
const modalSlug = `delete-${id}` const modalSlug = `delete-${id}`
@@ -86,12 +83,12 @@ const DeleteDocument: React.FC<Props> = (props) => {
setModified, setModified,
serverURL, serverURL,
api, api,
slug, collectionSlug,
id, id,
toggleModal, toggleModal,
modalSlug, modalSlug,
t, t,
singular, singularLabel,
i18n, i18n,
title, title,
history, history,
@@ -118,7 +115,7 @@ const DeleteDocument: React.FC<Props> = (props) => {
<Trans <Trans
i18nKey="aboutToDelete" i18nKey="aboutToDelete"
t={t} t={t}
values={{ label: getTranslation(singular, i18n), title: titleToRender }} values={{ label: getTranslation(singularLabel, i18n), title: titleToRender }}
> >
aboutToDelete aboutToDelete
<strong>{titleToRender}</strong> <strong>{titleToRender}</strong>

View File

@@ -2,7 +2,9 @@ import type { SanitizedCollectionConfig } from 'payload/types'
export type Props = { export type Props = {
buttonId?: string buttonId?: string
collection?: SanitizedCollectionConfig useAsTitle: SanitizedCollectionConfig['admin']['useAsTitle']
collectionSlug: SanitizedCollectionConfig['slug']
singularLabel: SanitizedCollectionConfig['labels']['singular']
id?: string id?: string
title?: string title?: string
} }

View File

@@ -70,6 +70,10 @@ export const DocumentControls: React.FC<{
{collectionConfig && !isEditing && !isAccountView && ( {collectionConfig && !isEditing && !isAccountView && (
<li className={`${baseClass}__list-item`}> <li className={`${baseClass}__list-item`}>
<p className={`${baseClass}__value`}> <p className={`${baseClass}__value`}>
Creating new{' '}
{typeof collectionConfig?.labels?.singular === 'string'
? collectionConfig.labels.singular
: 'Doc'}
{/* {t('creatingNewLabel', { {/* {t('creatingNewLabel', {
label: label:
typeof collectionConfig?.labels?.singular === 'string' typeof collectionConfig?.labels?.singular === 'string'
@@ -221,25 +225,31 @@ export const DocumentControls: React.FC<{
<PopupList.ButtonGroup> <PopupList.ButtonGroup>
{hasCreatePermission && ( {hasCreatePermission && (
<React.Fragment> <React.Fragment>
{/* <PopupList.Button <PopupList.Button
id="action-create" id="action-create"
to={`${adminRoute}/collections/${collectionConfig?.slug}/create`} to={`${adminRoute}/collections/${collectionConfig?.slug}/create`}
> >
{t('createNew')} Create New
</PopupList.Button> */} {/* {t('createNew')} */}
</PopupList.Button>
{/* {!collectionConfig?.admin?.disableDuplicate && isEditing && ( {!collectionConfig?.admin?.disableDuplicate && isEditing && (
<DuplicateDocument <DuplicateDocument
collection={collectionConfig} singularLabel={collectionConfig?.labels?.singular}
id={id} id={id}
slug={collectionConfig?.slug} slug={collectionConfig?.slug}
/> />
)} */} )}
</React.Fragment> </React.Fragment>
)} )}
{/* {hasDeletePermission && ( {hasDeletePermission && (
<DeleteDocument buttonId="action-delete" collection={collectionConfig} id={id} /> <DeleteDocument
)} */} buttonId="action-delete"
collectionSlug={collectionConfig?.slug}
useAsTitle={collectionConfig?.admin?.useAsTitle}
singularLabel={collectionConfig?.labels?.singular}
id={id}
/>
)}
</PopupList.ButtonGroup> </PopupList.ButtonGroup>
</Popup> </Popup>
)} )}

View File

@@ -43,7 +43,7 @@ export const DocumentHeader: React.FC<{
data={data} data={data}
isDate={titleFieldConfig?.type === 'date'} isDate={titleFieldConfig?.type === 'date'}
dateFormat={ dateFormat={
'date' in titleFieldConfig?.admin titleFieldConfig && 'date' in titleFieldConfig?.admin
? titleFieldConfig?.admin?.date?.displayFormat ? titleFieldConfig?.admin?.date?.displayFormat
: undefined : undefined
} }

View File

@@ -2,7 +2,6 @@
import { Modal, useModal } from '@faceless-ui/modal' import { Modal, useModal } from '@faceless-ui/modal'
import React, { useCallback, useState } from 'react' import React, { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useHistory } from 'react-router-dom'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import type { Props } from './types' import type { Props } from './types'
@@ -10,16 +9,17 @@ import type { Props } from './types'
import { getTranslation } from 'payload/utilities' import { getTranslation } from 'payload/utilities'
// import { requests } from '../../../api' // import { requests } from '../../../api'
import { useForm, useFormModified } from '../../forms/Form/context' import { useForm, useFormModified } from '../../forms/Form/context'
import { Minimal as MinimalTemplate } from '../../templates/Minimal' import { MinimalTemplate } from '../../templates/Minimal'
import { useConfig } from '../../providers/Config' import { useConfig } from '../../providers/Config'
import { Button } from '../Button' import { Button } from '../Button'
import * as PopupList from '../Popup/PopupButtonList' import * as PopupList from '../Popup/PopupButtonList'
import './index.scss' import './index.scss'
import { useRouter } from 'next/navigation'
const baseClass = 'duplicate' const baseClass = 'duplicate'
const Duplicate: React.FC<Props> = ({ id, collection, slug }) => { const Duplicate: React.FC<Props> = ({ id, slug, singularLabel }) => {
const { push } = useHistory() const { push } = useRouter()
const modified = useFormModified() const modified = useFormModified()
const { toggleModal } = useModal() const { toggleModal } = useModal()
const { setModified } = useForm() const { setModified } = useForm()
@@ -66,13 +66,14 @@ const Duplicate: React.FC<Props> = ({ id, collection, slug }) => {
// let data = await response.json() // let data = await response.json()
if (typeof collection.admin.hooks?.beforeDuplicate === 'function') { // TODO: convert this into a server action
data = await collection.admin.hooks.beforeDuplicate({ // if (typeof collection.admin.hooks?.beforeDuplicate === 'function') {
collection, // data = await collection.admin.hooks.beforeDuplicate({
data, // collection,
locale, // data,
}) // locale,
} // })
// }
if (!duplicateID) { if (!duplicateID) {
if ('createdAt' in data) delete data.createdAt if ('createdAt' in data) delete data.createdAt
@@ -133,10 +134,9 @@ const Duplicate: React.FC<Props> = ({ id, collection, slug }) => {
return return
} }
toast.success( toast.success(t('successfullyDuplicated', { label: getTranslation(singularLabel, i18n) }), {
t('successfullyDuplicated', { label: getTranslation(collection.labels.singular, i18n) }), autoClose: 3000,
{ autoClose: 3000 }, })
)
if (localeErrors.length > 0) { if (localeErrors.length > 0) {
toast.error( toast.error(
@@ -151,9 +151,7 @@ const Duplicate: React.FC<Props> = ({ id, collection, slug }) => {
setModified(false) setModified(false)
setTimeout(() => { setTimeout(() => {
push({ push(`${admin}/collections/${slug}/${duplicateID}`)
pathname: `${admin}/collections/${slug}/${duplicateID}`,
})
}, 10) }, 10)
}, },
[ [
@@ -161,7 +159,6 @@ const Duplicate: React.FC<Props> = ({ id, collection, slug }) => {
localization, localization,
t, t,
i18n, i18n,
collection,
setModified, setModified,
toggleModal, toggleModal,
modalSlug, modalSlug,

View File

@@ -1,7 +1,7 @@
import type { SanitizedCollectionConfig } from '../../../../collections/config/types' import type { SanitizedCollectionConfig } from 'payload/types'
export type Props = { export type Props = {
collection: SanitizedCollectionConfig singularLabel: SanitizedCollectionConfig['labels']['singular']
id: string id: string
slug: string slug: string
} }

View File

@@ -2,13 +2,18 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import { useAuth } from '../../providers/Auth' import { useAuth } from '../../providers/Auth'
import { Permissions, User } from 'payload/auth'
export const HydrateClientUser: React.FC<{ user: any }> = ({ user }) => { export const HydrateClientUser: React.FC<{ user: User; permissions: Permissions }> = ({
const { setUser } = useAuth() user,
permissions,
}) => {
const { setUser, setPermissions } = useAuth()
useEffect(() => { useEffect(() => {
setUser(user) setUser(user)
}, [user]) setPermissions(permissions)
}, [user, permissions, setUser, setPermissions])
return null return null
} }

View File

@@ -28,14 +28,18 @@ export const LoginForm: React.FC<{
action={`${api}/${userSlug}/login`} action={`${api}/${userSlug}/login`}
className={`${baseClass}__form`} className={`${baseClass}__form`}
disableSuccessStatus disableSuccessStatus
initialData={ initialState={{
prefillForm email: {
? { initialValue: prefillForm ? autoLogin.email : undefined,
email: autoLogin.email, value: prefillForm ? autoLogin.email : undefined,
password: autoLogin.password, valid: true,
} },
: undefined password: {
} initialValue: prefillForm ? autoLogin.password : undefined,
value: prefillForm ? autoLogin.password : undefined,
valid: true,
},
}}
method="POST" method="POST"
redirect={`${admin}${searchParams?.redirect || ''}`} redirect={`${admin}${searchParams?.redirect || ''}`}
waitForAutocomplete waitForAutocomplete

View File

@@ -1,3 +1,4 @@
'use client'
import type { LinkProps } from 'react-router-dom' import type { LinkProps } from 'react-router-dom'
import * as React from 'react' import * as React from 'react'
@@ -6,6 +7,7 @@ import Link from 'next/link' // TODO: abstract this out to support all routers
import './index.scss' import './index.scss'
const baseClass = 'popup-button-list' const baseClass = 'popup-button-list'
export const ButtonGroup: React.FC<{ export const ButtonGroup: React.FC<{
buttonSize?: 'default' | 'small' buttonSize?: 'default' | 'small'
children: React.ReactNode children: React.ReactNode
@@ -31,6 +33,7 @@ type MenuButtonProps = {
onClick?: () => void onClick?: () => void
to?: LinkProps['to'] to?: LinkProps['to']
} }
export const Button: React.FC<MenuButtonProps> = ({ export const Button: React.FC<MenuButtonProps> = ({
id, id,
active, active,

View File

@@ -8,3 +8,4 @@ export { useLocale } from '../providers/Locale'
export { useActions } from '../providers/ActionsProvider' export { useActions } from '../providers/ActionsProvider'
export { useAuth } from '../providers/Auth' export { useAuth } from '../providers/Auth'
export { useDocumentInfo } from '../providers/DocumentInfo' export { useDocumentInfo } from '../providers/DocumentInfo'
export { DocumentInfoProvider } from '../providers/DocumentInfo'

View File

@@ -45,12 +45,12 @@ export const addFieldStatePromise = async ({
user, user,
}: Args): Promise<void> => { }: Args): Promise<void> => {
if (fieldAffectsData(field)) { if (fieldAffectsData(field)) {
const validate = operation === 'update' ? field.validate : undefined
const fieldState: FormField = { const fieldState: FormField = {
// condition: field.admin?.condition,
initialValue: undefined, initialValue: undefined,
passesCondition, passesCondition,
valid: true, valid: true,
// validate: field.validate,
value: undefined, value: undefined,
} }
@@ -67,18 +67,18 @@ export const addFieldStatePromise = async ({
let validationResult: boolean | string = true let validationResult: boolean | string = true
// if (typeof fieldState.validate === 'function') { if (typeof validate === 'function') {
// validationResult = await fieldState.validate(data?.[field.name], { validationResult = await validate(data?.[field.name], {
// ...field, ...field,
// id, id,
// config, config,
// data: fullData, data: fullData,
// operation, operation,
// siblingData: data, siblingData: data,
// t, t,
// user, user,
// }) })
// } }
if (typeof validationResult === 'string') { if (typeof validationResult === 'string') {
fieldState.errorMessage = validationResult fieldState.errorMessage = validationResult

View File

@@ -4,8 +4,6 @@ import equal from 'deep-equal'
import type { FieldAction, Fields, FormField } from './types' import type { FieldAction, Fields, FormField } from './types'
import { deepCopyObject } from 'payload/utilities' import { deepCopyObject } from 'payload/utilities'
import getSiblingData from './getSiblingData'
import reduceFieldsToValues from './reduceFieldsToValues'
import { flattenRows, separateRows } from './rows' import { flattenRows, separateRows } from './rows'
/** /**
@@ -42,53 +40,14 @@ export function fieldReducer(state: Fields, action: FieldAction): Fields {
return newState return newState
} }
case 'MODIFY_CONDITION': {
const { path, result, user } = action
return Object.entries(state).reduce((newState, [fieldPath, field]) => {
if (fieldPath === path || fieldPath.indexOf(`${path}.`) === 0) {
let passesCondition = result
// If a condition is being set to true,
// Set all conditions to true
// Besides those who still fail their own conditions
if (passesCondition && field.condition) {
passesCondition = field.condition(
reduceFieldsToValues(state, true),
getSiblingData(state, path),
{ user },
)
}
return {
...newState,
[fieldPath]: {
...field,
passesCondition,
},
}
}
return {
...newState,
[fieldPath]: {
...field,
},
}
}, {})
}
case 'UPDATE': { case 'UPDATE': {
const newField = Object.entries(action).reduce( const newField = Object.entries(action).reduce(
(field, [key, value]) => { (field, [key, value]) => {
if ( if (
[ [
'condition',
'disableFormData', 'disableFormData',
'errorMessage', 'errorMessage',
'initialValue', 'initialValue',
'passesCondition',
'rows', 'rows',
'valid', 'valid',
'validate', 'validate',

View File

@@ -31,7 +31,6 @@ import { useLocale } from '../../providers/Locale'
import { useOperation } from '../../providers/OperationProvider' import { useOperation } from '../../providers/OperationProvider'
import { WatchFormErrors } from './WatchFormErrors' import { WatchFormErrors } from './WatchFormErrors'
import { buildFieldSchemaMap } from './buildFieldSchemaMap' import { buildFieldSchemaMap } from './buildFieldSchemaMap'
import buildInitialState from './buildInitialState'
import buildStateFromSchema from './buildStateFromSchema' import buildStateFromSchema from './buildStateFromSchema'
import { import {
FormContext, FormContext,
@@ -51,7 +50,7 @@ import reduceFieldsToValues from './reduceFieldsToValues'
const baseClass = 'form' const baseClass = 'form'
const Form: React.FC<Props> = (props) => { const Form: React.FC<Props> = (props) => {
const { id, collection, getDocPreferences, global } = useDocumentInfo() const { id, collectionSlug, getDocPreferences, globalSlug } = useDocumentInfo()
const { const {
action, action,
@@ -59,9 +58,9 @@ const Form: React.FC<Props> = (props) => {
className, className,
disableSuccessStatus, disableSuccessStatus,
disabled, disabled,
fields: fieldsFromProps = collection?.fields || global?.fields, fields: fieldsFromProps,
// fields: fieldsFromProps = collection?.fields || global?.fields,
handleResponse, handleResponse,
initialData, // values only, paths are required as key - form should build initial state as convenience
initialState, // fully formed initial field state initialState, // fully formed initial field state
method, method,
onSubmit, onSubmit,
@@ -83,17 +82,10 @@ const Form: React.FC<Props> = (props) => {
const [modified, setModified] = useState(false) const [modified, setModified] = useState(false)
const [processing, setProcessing] = useState(false) const [processing, setProcessing] = useState(false)
const [submitted, setSubmitted] = useState(false) const [submitted, setSubmitted] = useState(false)
const [formattedInitialData, setFormattedInitialData] = useState(buildInitialState(initialData))
const formRef = useRef<HTMLFormElement>(null) const formRef = useRef<HTMLFormElement>(null)
const contextRef = useRef({} as FormContextType) const contextRef = useRef({} as FormContextType)
let initialFieldState = {} const fieldsReducer = useReducer(fieldReducer, {}, () => initialState)
if (formattedInitialData) initialFieldState = formattedInitialData
if (initialState) initialFieldState = initialState
const fieldsReducer = useReducer(fieldReducer, {}, () => initialFieldState)
/** /**
* `fields` is the current, up-to-date state/data of all fields in the form. It can be modified by using dispatchFields, * `fields` is the current, up-to-date state/data of all fields in the form. It can be modified by using dispatchFields,
* which calls the fieldReducer, which then updates the state. * which calls the fieldReducer, which then updates the state.
@@ -642,15 +634,6 @@ const Form: React.FC<Props> = (props) => {
} }
}, [initialState, dispatchFields]) }, [initialState, dispatchFields])
useEffect(() => {
if (initialData) {
contextRef.current = { ...initContextState } as FormContextType
const builtState = buildInitialState(initialData)
setFormattedInitialData(builtState)
dispatchFields({ state: builtState, type: 'REPLACE_STATE' })
}
}, [initialData, dispatchFields])
useThrottledEffect( useThrottledEffect(
() => { () => {
refreshCookie() refreshCookie()

View File

@@ -2,7 +2,7 @@ import type React from 'react'
import type { Dispatch } from 'react' import type { Dispatch } from 'react'
import type { User } from 'payload/auth' import type { User } from 'payload/auth'
import type { Condition, Field, Field as FieldConfig, Validate } from 'payload/types' import type { Field, Field as FieldConfig, Validate } from 'payload/types'
export type Row = { export type Row = {
blockType?: string blockType?: string
@@ -12,15 +12,14 @@ export type Row = {
} }
export type FormField = { export type FormField = {
// condition?: Condition
disableFormData?: boolean disableFormData?: boolean
errorMessage?: string errorMessage?: string
initialValue: unknown initialValue: unknown
passesCondition?: boolean passesCondition?: boolean
rows?: Row[] rows?: Row[]
valid: boolean valid: boolean
// validate?: Validate
value: unknown value: unknown
validate?: Validate
} }
export type Fields = { export type Fields = {
@@ -48,7 +47,6 @@ export type Props = {
*/ */
fields?: Field[] fields?: Field[]
handleResponse?: (res: Response) => void handleResponse?: (res: Response) => void
initialData?: Data
initialState?: Fields initialState?: Fields
log?: boolean log?: boolean
method?: 'DELETE' | 'GET' | 'PATCH' | 'POST' method?: 'DELETE' | 'GET' | 'PATCH' | 'POST'

View File

@@ -56,6 +56,8 @@ const RenderFields: React.FC<Props> = (props) => {
permissions: fieldPermissions, permissions: fieldPermissions,
data, data,
user, user,
valid: fieldState?.valid,
errorMessage: fieldState?.errorMessage,
} }
if (field) { if (field) {

View File

@@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'
import type { Props } from './types' import type { Props } from './types'
import { array } from 'payload/fields/validations'
import { getTranslation } from 'payload/utilities' import { getTranslation } from 'payload/utilities'
import { scrollToID } from '../../../utilities/scrollToID' import { scrollToID } from '../../../utilities/scrollToID'
import Banner from '../../../elements/Banner' import Banner from '../../../elements/Banner'
@@ -40,7 +39,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
path: pathFromProps, path: pathFromProps,
permissions, permissions,
required, required,
validate = array, validate,
} = props } = props
const path = pathFromProps || name const path = pathFromProps || name
@@ -93,7 +92,6 @@ const ArrayFieldType: React.FC<Props> = (props) => {
valid, valid,
value, value,
} = useField<number>({ } = useField<number>({
condition,
hasRows: true, hasRows: true,
path, path,
validate: memoizedValidate, validate: memoizedValidate,

View File

@@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'
import type { Props } from './types' import type { Props } from './types'
import { blocks as blocksValidator } from 'payload/fields/validations'
import { getTranslation } from 'payload/utilities' import { getTranslation } from 'payload/utilities'
import { scrollToID } from '../../../utilities/scrollToID' import { scrollToID } from '../../../utilities/scrollToID'
import Banner from '../../../elements/Banner' import Banner from '../../../elements/Banner'
@@ -34,7 +33,7 @@ const BlocksField: React.FC<Props> = (props) => {
const { const {
name, name,
admin: { className, condition, description, readOnly }, admin: { className, description, readOnly },
blocks, blocks,
fieldTypes, fieldTypes,
forceRender = false, forceRender = false,
@@ -47,7 +46,7 @@ const BlocksField: React.FC<Props> = (props) => {
path: pathFromProps, path: pathFromProps,
permissions, permissions,
required, required,
validate = blocksValidator, validate,
} = props } = props
const path = pathFromProps || name const path = pathFromProps || name
@@ -92,7 +91,6 @@ const BlocksField: React.FC<Props> = (props) => {
valid, valid,
value, value,
} = useField<number>({ } = useField<number>({
condition,
hasRows: true, hasRows: true,
path, path,
validate: memoizedValidate, validate: memoizedValidate,

View File

@@ -1,10 +1,10 @@
'use client' 'use client'
import React, { useCallback } from 'react' import React, { Fragment, useCallback } from 'react'
import './index.scss'
import useField from '../../useField' import useField from '../../useField'
import { Validate } from 'payload/types'
const baseClass = 'checkbox-input' import { Check } from '../../../icons/Check'
import { Line } from '../../../icons/Line'
type CheckboxInputProps = { type CheckboxInputProps = {
'aria-label'?: string 'aria-label'?: string
@@ -15,10 +15,12 @@ type CheckboxInputProps = {
label?: string label?: string
name?: string name?: string
onChange?: (value: boolean) => void onChange?: (value: boolean) => void
partialChecked?: boolean
readOnly?: boolean readOnly?: boolean
required?: boolean required?: boolean
path: string path: string
validate?: Validate
partialChecked?: boolean
iconClassName?: string
} }
export const CheckboxInput: React.FC<CheckboxInputProps> = (props) => { export const CheckboxInput: React.FC<CheckboxInputProps> = (props) => {
@@ -28,24 +30,27 @@ export const CheckboxInput: React.FC<CheckboxInputProps> = (props) => {
'aria-label': ariaLabel, 'aria-label': ariaLabel,
checked: checkedFromProps, checked: checkedFromProps,
className, className,
iconClassName,
inputRef, inputRef,
onChange: onChangeFromProps, onChange: onChangeFromProps,
readOnly, readOnly,
required, required,
path, path,
validate,
partialChecked,
} = props } = props
// const memoizedValidate = useCallback( const memoizedValidate: Validate = useCallback(
// (value, options) => { (value, options) => {
// return validate(value, { ...options, required }) if (typeof validate === 'function') return validate(value, { ...options, required })
// }, },
// [validate, required], [validate, required],
// ) )
const { errorMessage, setValue, showError, value } = useField({ const { setValue, value } = useField({
// disableFormData, // disableFormData,
path, path,
// validate: memoizedValidate, validate: memoizedValidate,
}) })
const onToggle = useCallback(() => { const onToggle = useCallback(() => {
@@ -58,17 +63,27 @@ export const CheckboxInput: React.FC<CheckboxInputProps> = (props) => {
const checked = checkedFromProps || Boolean(value) const checked = checkedFromProps || Boolean(value)
return ( return (
<input <Fragment>
className={className} <input
aria-label={ariaLabel} className={className}
defaultChecked={checked} aria-label={ariaLabel}
disabled={readOnly} defaultChecked={Boolean(checked)}
id={id} disabled={readOnly}
name={name} id={id}
onInput={onToggle} name={name}
ref={inputRef} onInput={onToggle}
type="checkbox" ref={inputRef}
required={required} type="checkbox"
/> required={required}
/>
<span
className={[iconClassName, !value && partialChecked ? 'check' : 'partial']
.filter(Boolean)
.join(' ')}
>
{value && <Check />}
{!value && partialChecked && <Line />}
</span>
</Fragment>
) )
} }

View File

@@ -0,0 +1,30 @@
'use client'
import React from 'react'
import { useFormFields } from '../../Form/context'
import './index.scss'
export const CheckboxWrapper: React.FC<{
path: string
children: React.ReactNode
readOnly?: boolean
baseClass?: string
}> = (props) => {
const { path, children, readOnly, baseClass } = props
const { value: checked } = useFormFields(([fields]) => fields[path])
return (
<div
className={[
baseClass,
checked && `${baseClass}--checked`,
readOnly && `${baseClass}--read-only`,
]
.filter(Boolean)
.join(' ')}
>
{children}
</div>
)
}

View File

@@ -2,15 +2,16 @@ import React from 'react'
import type { Props } from './types' import type { Props } from './types'
import { checkbox } from 'payload/fields/validations'
import DefaultError from '../../Error' import DefaultError from '../../Error'
import FieldDescription from '../../FieldDescription' import FieldDescription from '../../FieldDescription'
import { fieldBaseClass } from '../shared' import { fieldBaseClass } from '../shared'
import { CheckboxInput } from './Input' import { CheckboxInput } from './Input'
import DefaultLabel from '../../Label' import DefaultLabel from '../../Label'
import './index.scss' import './index.scss'
import { CheckboxWrapper } from './Wrapper'
const baseClass = 'checkbox' const baseClass = 'checkbox'
const inputBaseClass = 'checkbox-input'
const Checkbox: React.FC<Props> = (props) => { const Checkbox: React.FC<Props> = (props) => {
const { const {
@@ -25,10 +26,11 @@ const Checkbox: React.FC<Props> = (props) => {
} = {}, } = {},
disableFormData, disableFormData,
label, label,
onChange,
path: pathFromProps, path: pathFromProps,
required, required,
validate = checkbox, valid,
errorMessage,
value,
} = props } = props
const path = pathFromProps || name const path = pathFromProps || name
@@ -43,9 +45,9 @@ const Checkbox: React.FC<Props> = (props) => {
className={[ className={[
fieldBaseClass, fieldBaseClass,
baseClass, baseClass,
// showError && 'error', !valid && 'error',
className, className,
// value && `${baseClass}--checked`, value && `${baseClass}--checked`,
readOnly && `${baseClass}--read-only`, readOnly && `${baseClass}--read-only`,
] ]
.filter(Boolean) .filter(Boolean)
@@ -56,23 +58,10 @@ const Checkbox: React.FC<Props> = (props) => {
}} }}
> >
<div className={`${baseClass}__error-wrap`}> <div className={`${baseClass}__error-wrap`}>
<ErrorComp <ErrorComp alignCaret="left" message={errorMessage} showError={!valid} />
alignCaret="left"
// message={errorMessage}
// showError={showError}
/>
</div> </div>
<div <CheckboxWrapper path={path} readOnly={readOnly} baseClass={inputBaseClass}>
className={[ <div className={`${inputBaseClass}__input`}>
baseClass,
className,
// (checked || partialChecked) && `${baseClass}--checked`,
readOnly && `${baseClass}--read-only`,
]
.filter(Boolean)
.join(' ')}
>
<div className={`${baseClass}__input`}>
{Array.isArray(beforeInput) && beforeInput.map((Component, i) => <Component key={i} />)} {Array.isArray(beforeInput) && beforeInput.map((Component, i) => <Component key={i} />)}
<CheckboxInput <CheckboxInput
id={fieldID} id={fieldID}
@@ -82,20 +71,13 @@ const Checkbox: React.FC<Props> = (props) => {
readOnly={readOnly} readOnly={readOnly}
required={required} required={required}
path={path} path={path}
iconClassName={`${inputBaseClass}__icon`}
/> />
{Array.isArray(afterInput) && afterInput.map((Component, i) => <Component key={i} />)} {Array.isArray(afterInput) && afterInput.map((Component, i) => <Component key={i} />)}
{/* <span className={`${baseClass}__icon ${!partialChecked ? 'check' : 'partial'}`}>
{!partialChecked && <Check />}
{partialChecked && <Line />}
</span> */}
</div> </div>
{label && <LabelComp htmlFor={fieldID} label={label} required={required} />} {label && <LabelComp htmlFor={fieldID} label={label} required={required} />}
</div> </CheckboxWrapper>
<FieldDescription <FieldDescription description={description} path={path} value={value} />
description={description}
path={path}
// value={value}
/>
</div> </div>
) )
} }

View File

@@ -1,7 +1,8 @@
import type { CheckboxField } from 'payload/types' import type { CheckboxField } from 'payload/types'
import { FormFieldBase } from '../Text/types'
export type Props = Omit<CheckboxField, 'type'> & { export type Props = FormFieldBase &
disableFormData?: boolean Omit<CheckboxField, 'type'> & {
onChange?: (val: boolean) => void disableFormData?: boolean
path?: string onChange?: (val: boolean) => void
} }

View File

@@ -3,7 +3,6 @@ import React, { useCallback } from 'react'
import type { Props } from './types' import type { Props } from './types'
import { code } from 'payload/fields/validations'
import { CodeEditor } from '../../../elements/CodeEditor' import { CodeEditor } from '../../../elements/CodeEditor'
import DefaultError from '../../Error' import DefaultError from '../../Error'
import FieldDescription from '../../FieldDescription' import FieldDescription from '../../FieldDescription'
@@ -35,7 +34,7 @@ const Code: React.FC<Props> = (props) => {
label, label,
path: pathFromProps, path: pathFromProps,
required, required,
validate = code, validate,
} = props } = props
const ErrorComp = Error || DefaultError const ErrorComp = Error || DefaultError

View File

@@ -1,8 +1,8 @@
'use client' 'use client'
import React from 'react' import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import type { DateField } from 'payload/types' import type { DateField, Validate } from 'payload/types'
import { getTranslation } from 'payload/utilities' import { getTranslation } from 'payload/utilities'
import DatePicker from '../../../elements/DatePicker' import DatePicker from '../../../elements/DatePicker'
@@ -18,21 +18,20 @@ export type DateTimeInputProps = Omit<DateField, 'admin' | 'name' | 'type'> & {
} }
export const DateTimeInput: React.FC<DateTimeInputProps> = (props) => { export const DateTimeInput: React.FC<DateTimeInputProps> = (props) => {
const { path, readOnly, placeholder, datePickerProps, style, width } = props const { path, readOnly, placeholder, datePickerProps, style, width, validate, required } = props
const { i18n } = useTranslation() const { i18n } = useTranslation()
// const memoizedValidate = useCallback( const memoizedValidate: Validate = useCallback(
// (value, options) => { (value, options) => {
// return validate(value, { ...options, required }) if (typeof validate === 'function') return validate(value, { ...options, required })
// }, },
// [validate, required], [validate, required],
// ) )
const { errorMessage, setValue, showError, value } = useField<Date>({ const { errorMessage, setValue, showError, value } = useField<Date>({
// condition,
path, path,
// validate: memoizedValidate, validate: memoizedValidate,
}) })
return ( return (

View File

@@ -2,7 +2,6 @@ import React from 'react'
import type { Props } from './types' import type { Props } from './types'
import { date as dateValidation } from 'payload/fields/validations'
import { DateTimeInput } from './Input' import { DateTimeInput } from './Input'
import './index.scss' import './index.scss'
import FieldDescription from '../../FieldDescription' import FieldDescription from '../../FieldDescription'
@@ -28,7 +27,6 @@ const DateTime: React.FC<Props> = (props) => {
label, label,
path: pathFromProps, path: pathFromProps,
required, required,
validate = dateValidation,
} = props } = props
const path = pathFromProps || name const path = pathFromProps || name

View File

@@ -1,10 +1,11 @@
'use client' 'use client'
import React from 'react' import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import useField from '../../useField' import useField from '../../useField'
import './index.scss' import './index.scss'
import { getTranslation } from 'payload/utilities' import { getTranslation } from 'payload/utilities'
import { Validate } from 'payload/types'
export const EmailInput: React.FC<{ export const EmailInput: React.FC<{
name: string name: string
@@ -13,6 +14,7 @@ export const EmailInput: React.FC<{
path: string path: string
required?: boolean required?: boolean
placeholder?: Record<string, string> | string placeholder?: Record<string, string> | string
validate?: Validate
}> = (props) => { }> = (props) => {
const { const {
name, name,
@@ -20,7 +22,7 @@ export const EmailInput: React.FC<{
readOnly, readOnly,
path: pathFromProps, path: pathFromProps,
required, required,
// validate = email, validate,
placeholder, placeholder,
} = props } = props
@@ -28,12 +30,12 @@ export const EmailInput: React.FC<{
const path = pathFromProps || name const path = pathFromProps || name
// const memoizedValidate = useCallback( const memoizedValidate: Validate = useCallback(
// (value, options) => { (value, options) => {
// return validate(value, { ...options, required }) if (typeof validate === 'function') return validate(value, { ...options, required })
// }, },
// [validate, required], [validate, required],
// ) )
const { const {
// errorMessage, // errorMessage,
@@ -42,7 +44,7 @@ export const EmailInput: React.FC<{
value, value,
} = useField({ } = useField({
path, path,
// validate: memoizedValidate, validate: memoizedValidate,
}) })
return ( return (

View File

@@ -2,7 +2,6 @@ import React from 'react'
import type { Props } from './types' import type { Props } from './types'
import { email } from 'payload/fields/validations'
import DefaultError from '../../Error' import DefaultError from '../../Error'
import FieldDescription from '../../FieldDescription' import FieldDescription from '../../FieldDescription'
import DefaultLabel from '../../Label' import DefaultLabel from '../../Label'
@@ -25,7 +24,6 @@ export const Email: React.FC<Props> = (props) => {
label, label,
path: pathFromProps, path: pathFromProps,
required, required,
validate = email,
} = props } = props
const path = pathFromProps || name const path = pathFromProps || name
@@ -59,7 +57,6 @@ export const Email: React.FC<Props> = (props) => {
<EmailInput <EmailInput
name={name} name={name}
autoComplete={autoComplete} autoComplete={autoComplete}
// condition={condition}
readOnly={readOnly} readOnly={readOnly}
path={path} path={path}
required={required} required={required}

View File

@@ -3,7 +3,6 @@ import React, { useCallback, useEffect, useState } from 'react'
import type { Props } from './types' import type { Props } from './types'
import { json } from 'payload/fields/validations'
import { CodeEditor } from '../../../elements/CodeEditor' import { CodeEditor } from '../../../elements/CodeEditor'
import DefaultError from '../../Error' import DefaultError from '../../Error'
import FieldDescription from '../../FieldDescription' import FieldDescription from '../../FieldDescription'
@@ -11,6 +10,7 @@ import DefaultLabel from '../../Label'
import useField from '../../useField' import useField from '../../useField'
import { fieldBaseClass } from '../shared' import { fieldBaseClass } from '../shared'
import './index.scss' import './index.scss'
import { Validate } from 'payload/types'
const baseClass = 'json-field' const baseClass = 'json-field'
@@ -20,7 +20,6 @@ const JSONField: React.FC<Props> = (props) => {
admin: { admin: {
className, className,
components: { Error, Label } = {}, components: { Error, Label } = {},
condition,
description, description,
editorOptions, editorOptions,
readOnly, readOnly,
@@ -30,7 +29,7 @@ const JSONField: React.FC<Props> = (props) => {
label, label,
path: pathFromProps, path: pathFromProps,
required, required,
validate = json, validate,
} = props } = props
const ErrorComp = Error || DefaultError const ErrorComp = Error || DefaultError
@@ -41,15 +40,15 @@ const JSONField: React.FC<Props> = (props) => {
const [jsonError, setJsonError] = useState<string>() const [jsonError, setJsonError] = useState<string>()
const [hasLoadedValue, setHasLoadedValue] = useState(false) const [hasLoadedValue, setHasLoadedValue] = useState(false)
const memoizedValidate = useCallback( const memoizedValidate: Validate = useCallback(
(value, options) => { (value, options) => {
return validate(value, { ...options, jsonError, required }) if (typeof validate === 'function')
return validate(value, { ...options, jsonError, required })
}, },
[validate, required, jsonError], [validate, required],
) )
const { errorMessage, initialValue, setValue, showError, value } = useField<string>({ const { errorMessage, initialValue, setValue, showError, value } = useField<string>({
condition,
path, path,
validate: memoizedValidate, validate: memoizedValidate,
}) })

View File

@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'
import { getTranslation } from 'payload/utilities' import { getTranslation } from 'payload/utilities'
import useField from '../../useField' import useField from '../../useField'
import './index.scss' import './index.scss'
import { Validate } from 'payload/types'
export const NumberInput: React.FC<{ export const NumberInput: React.FC<{
path: string path: string
@@ -16,6 +17,7 @@ export const NumberInput: React.FC<{
step?: number step?: number
hasMany?: boolean hasMany?: boolean
name?: string name?: string
validate?: Validate
}> = (props) => { }> = (props) => {
const { const {
name, name,
@@ -27,23 +29,23 @@ export const NumberInput: React.FC<{
min, min,
path: pathFromProps, path: pathFromProps,
required, required,
validate,
} = props } = props
const { i18n, t } = useTranslation() const { i18n, t } = useTranslation()
const path = pathFromProps || name const path = pathFromProps || name
// const memoizedValidate = useCallback( const memoizedValidate = useCallback(
// (value, options) => { (value, options) => {
// return validate(value, { ...options, max, min, required }) return validate(value, { ...options, max, min, required })
// }, },
// [validate, min, max, required], [validate, min, max, required],
// ) )
const { errorMessage, setValue, showError, value } = useField<number | number[]>({ const { errorMessage, setValue, showError, value } = useField<number | number[]>({
// condition,
path, path,
// validate: memoizedValidate, validate: memoizedValidate,
}) })
const handleChange = useCallback( const handleChange = useCallback(

View File

@@ -1,9 +1,7 @@
import React from 'react' import React from 'react'
import type { Option } from '../../../elements/ReactSelect/types'
import type { Props } from './types' import type { Props } from './types'
import { number } from 'payload/fields/validations'
import { isNumber } from 'payload/utilities' import { isNumber } from 'payload/utilities'
import ReactSelect from '../../../elements/ReactSelect' import ReactSelect from '../../../elements/ReactSelect'
import DefaultError from '../../Error' import DefaultError from '../../Error'
@@ -36,7 +34,7 @@ const NumberField: React.FC<Props> = (props) => {
minRows, minRows,
path: pathFromProps, path: pathFromProps,
required, required,
validate = number, validate,
} = props } = props
const ErrorComp = Error || DefaultError const ErrorComp = Error || DefaultError
@@ -44,10 +42,16 @@ const NumberField: React.FC<Props> = (props) => {
const path = pathFromProps || name const path = pathFromProps || name
const memoizedValidate = React.useCallback(
(value, options) => {
return validate(value, { ...options, required })
},
[validate, required],
)
const { errorMessage, setValue, showError, value } = useField<number | number[]>({ const { errorMessage, setValue, showError, value } = useField<number | number[]>({
condition,
path, path,
// validate: memoizedValidate, validate: memoizedValidate,
}) })
return ( return (

View File

@@ -1,11 +1,9 @@
'use client' 'use client'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import type { Props } from './types'
import { password } from 'payload/fields/validations'
import useField from '../../useField' import useField from '../../useField'
import './index.scss' import './index.scss'
import { Validate } from 'payload/types'
export const PasswordInput: React.FC<{ export const PasswordInput: React.FC<{
name: string name: string
@@ -13,28 +11,22 @@ export const PasswordInput: React.FC<{
disabled?: boolean disabled?: boolean
path: string path: string
required?: boolean required?: boolean
validate?: Validate
}> = (props) => { }> = (props) => {
const { const { name, autoComplete, disabled, path: pathFromProps, required, validate } = props
name,
autoComplete,
disabled,
path: pathFromProps,
// required,
} = props
const path = pathFromProps || name const path = pathFromProps || name
// const memoizedValidate = useCallback( const memoizedValidate: Validate = useCallback(
// (value, options) => { (value, options) => {
// const validationResult = validate(value, { ...options, required }) if (typeof validate === 'function') return validate(value, { ...options, required })
// return validationResult },
// }, [validate, required],
// [validate, required], )
// )
const { errorMessage, formProcessing, setValue, showError, value } = useField({ const { errorMessage, formProcessing, setValue, showError, value } = useField({
path, path,
// validate: memoizedValidate, validate: memoizedValidate,
}) })
return ( return (

View File

@@ -2,7 +2,6 @@ import React from 'react'
import type { Props } from './types' import type { Props } from './types'
import { password } from 'payload/fields/validations'
import Error from '../../Error' import Error from '../../Error'
import Label from '../../Label' import Label from '../../Label'
import './index.scss' import './index.scss'
@@ -19,7 +18,6 @@ export const Password: React.FC<Props> = (props) => {
path: pathFromProps, path: pathFromProps,
required, required,
style, style,
validate = password,
width, width,
} = props } = props

View File

@@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'
import type { Props } from './types' import type { Props } from './types'
import { point } from 'payload/fields/validations'
import { getTranslation } from 'payload/utilities' import { getTranslation } from 'payload/utilities'
import DefaultError from '../../Error' import DefaultError from '../../Error'
import FieldDescription from '../../FieldDescription' import FieldDescription from '../../FieldDescription'
@@ -12,6 +11,7 @@ import DefaultLabel from '../../Label'
import useField from '../../useField' import useField from '../../useField'
import { fieldBaseClass } from '../shared' import { fieldBaseClass } from '../shared'
import './index.scss' import './index.scss'
import { Validate } from 'payload/types'
const baseClass = 'point' const baseClass = 'point'
@@ -32,7 +32,7 @@ const PointField: React.FC<Props> = (props) => {
label, label,
path: pathFromProps, path: pathFromProps,
required, required,
validate = point, validate,
} = props } = props
const ErrorComp = Error || DefaultError const ErrorComp = Error || DefaultError
@@ -42,9 +42,9 @@ const PointField: React.FC<Props> = (props) => {
const { i18n, t } = useTranslation('fields') const { i18n, t } = useTranslation('fields')
const memoizedValidate = useCallback( const memoizedValidate: Validate = useCallback(
(value, options) => { (value, options) => {
return validate(value, { ...options, required }) if (typeof validate === 'function') return validate(value, { ...options, required })
}, },
[validate, required], [validate, required],
) )
@@ -55,7 +55,6 @@ const PointField: React.FC<Props> = (props) => {
showError, showError,
value = [null, null], value = [null, null],
} = useField<[number, number]>({ } = useField<[number, number]>({
condition,
path, path,
validate: memoizedValidate, validate: memoizedValidate,
}) })

View File

@@ -3,7 +3,6 @@ import React, { useCallback } from 'react'
import type { Props } from './types' import type { Props } from './types'
import { radio } from 'payload/fields/validations'
import useField from '../../useField' import useField from '../../useField'
import RadioGroupInput from './Input' import RadioGroupInput from './Input'
@@ -24,20 +23,20 @@ const RadioGroup: React.FC<Props> = (props) => {
options, options,
path: pathFromProps, path: pathFromProps,
required, required,
validate = radio, validate,
} = props } = props
const path = pathFromProps || name const path = pathFromProps || name
const memoizedValidate = useCallback( const memoizedValidate = useCallback(
(value, validationOptions) => { (value, validationOptions) => {
return validate(value, { ...validationOptions, options, required }) if (typeof validate === 'function')
return validate(value, { ...validationOptions, options, required })
}, },
[validate, options, required], [validate, options, required],
) )
const { errorMessage, setValue, showError, value } = useField<string>({ const { errorMessage, setValue, showError, value } = useField<string>({
condition,
path, path,
validate: memoizedValidate, validate: memoizedValidate,
}) })

View File

@@ -8,7 +8,6 @@ import type { Where } from 'payload/types'
import type { DocumentDrawerProps } from '../../../elements/DocumentDrawer/types' import type { DocumentDrawerProps } from '../../../elements/DocumentDrawer/types'
import type { FilterOptionsResult, GetResults, Option, Props, Value } from './types' import type { FilterOptionsResult, GetResults, Option, Props, Value } from './types'
import { relationship } from 'payload/fields/validations'
import { wordBoundariesRegex } from 'payload/utilities' import { wordBoundariesRegex } from 'payload/utilities'
import { useDebouncedCallback } from '../../../hooks/useDebouncedCallback' import { useDebouncedCallback } from '../../../hooks/useDebouncedCallback'
import ReactSelect from '../../../elements/ReactSelect' import ReactSelect from '../../../elements/ReactSelect'
@@ -55,7 +54,7 @@ const Relationship: React.FC<Props> = (props) => {
path, path,
relationTo, relationTo,
required, required,
validate = relationship, validate,
} = props } = props
const ErrorComp = Error || DefaultError const ErrorComp = Error || DefaultError
@@ -94,7 +93,6 @@ const Relationship: React.FC<Props> = (props) => {
) )
const { errorMessage, initialValue, setValue, showError, value } = useField<Value | Value[]>({ const { errorMessage, initialValue, setValue, showError, value } = useField<Value | Value[]>({
condition,
path: pathOrName, path: pathOrName,
validate: memoizedValidate, validate: memoizedValidate,
}) })

View File

@@ -2,7 +2,7 @@
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import type { OptionObject } from 'payload/types' import type { OptionObject, Validate } from 'payload/types'
import type { Option } from '../../../elements/ReactSelect/types' import type { Option } from '../../../elements/ReactSelect/types'
import { getTranslation } from 'payload/utilities' import { getTranslation } from 'payload/utilities'
@@ -17,19 +17,22 @@ const SelectInput: React.FC<{
isSortable: boolean isSortable: boolean
options: OptionObject[] options: OptionObject[]
path: string path: string
}> = ({ readOnly, isClearable, hasMany, isSortable, options, path }) => { validate?: Validate
required?: boolean
}> = ({ readOnly, isClearable, hasMany, isSortable, options, path, validate, required }) => {
const { i18n } = useTranslation() const { i18n } = useTranslation()
// const memoizedValidate = useCallback( const memoizedValidate: Validate = useCallback(
// (value, validationOptions) => { (value, validationOptions) => {
// return validate(value, { ...validationOptions, hasMany, options, required }) if (typeof validate === 'function')
// }, return validate(value, { ...validationOptions, hasMany, options, required })
// [validate, required, hasMany, options], },
// ) [validate, required],
)
const { errorMessage, setValue, showError, value } = useField({ const { errorMessage, setValue, showError, value } = useField({
path, path,
// validate: memoizedValidate, validate: memoizedValidate,
}) })
let valueToRender let valueToRender

View File

@@ -39,7 +39,6 @@ export const Select: React.FC<Props> = (props) => {
options, options,
path: pathFromProps, path: pathFromProps,
required, required,
// validate = select,
} = props } = props
const path = pathFromProps || name const path = pathFromProps || name

View File

@@ -1,8 +1,8 @@
'use client' 'use client'
import React from 'react' import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import type { SanitizedConfig } from 'payload/types' import type { SanitizedConfig, Validate } from 'payload/types'
import { getTranslation } from 'payload/utilities' import { getTranslation } from 'payload/utilities'
import { isFieldRTL } from '../shared' import { isFieldRTL } from '../shared'
@@ -22,6 +22,8 @@ export const TextInput: React.FC<{
rtl?: boolean rtl?: boolean
maxLength?: number maxLength?: number
minLength?: number minLength?: number
validate?: Validate
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void
}> = (props) => { }> = (props) => {
const { const {
path, path,
@@ -30,29 +32,30 @@ export const TextInput: React.FC<{
localized, localized,
localizationConfig, localizationConfig,
rtl, rtl,
// maxLength, maxLength,
// minLength, minLength,
validate,
required,
onKeyDown,
} = props } = props
const { i18n } = useTranslation() const { i18n } = useTranslation()
const locale = useLocale() const locale = useLocale()
const { const memoizedValidate: Validate = useCallback(
// errorMessage, (value, options) => {
setValue, if (typeof validate === 'function')
// showError, return validate(value, { ...options, maxLength, minLength, required })
value, },
} = useField({ [validate, minLength, maxLength, required],
)
const field = useField({
path, path,
// validate: memoizedValidate, validate: memoizedValidate,
}) })
// const memoizedValidate = useCallback( const { setValue, value } = field
// (value, options) => {
// return validate(value, { ...options, maxLength, minLength, required })
// },
// [validate, minLength, maxLength, required],
// )
const renderRTL = isFieldRTL({ const renderRTL = isFieldRTL({
fieldLocalized: localized, fieldLocalized: localized,
@@ -70,7 +73,7 @@ export const TextInput: React.FC<{
onChange={(e) => { onChange={(e) => {
setValue(e.target.value) setValue(e.target.value)
}} }}
// onKeyDown={onKeyDown} onKeyDown={onKeyDown}
placeholder={getTranslation(placeholder, i18n)} placeholder={getTranslation(placeholder, i18n)}
// ref={inputRef} // ref={inputRef}
type="text" type="text"

View File

@@ -2,7 +2,6 @@ import React from 'react'
import type { Props } from './types' import type { Props } from './types'
import { text } from 'payload/fields/validations'
import { fieldBaseClass } from '../shared' import { fieldBaseClass } from '../shared'
import { TextInput } from './Input' import { TextInput } from './Input'
import FieldDescription from '../../FieldDescription' import FieldDescription from '../../FieldDescription'
@@ -28,7 +27,8 @@ const Text: React.FC<Props> = (props) => {
minLength, minLength,
path: pathFromProps, path: pathFromProps,
required, required,
validate = text, valid,
errorMessage,
} = props } = props
const path = pathFromProps || name const path = pathFromProps || name
@@ -38,12 +38,7 @@ const Text: React.FC<Props> = (props) => {
return ( return (
<div <div
className={[ className={[fieldBaseClass, 'text', className, !valid && 'error', readOnly && 'read-only']
fieldBaseClass,
'text',
className,
// showError && 'error', readOnly && 'read-only'
]
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
style={{ style={{
@@ -51,10 +46,7 @@ const Text: React.FC<Props> = (props) => {
width, width,
}} }}
> >
<ErrorComp <ErrorComp message={errorMessage} showError={!valid} />
// message={errorMessage}
// showError={showError}
/>
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} /> <LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
<div className="input-wrapper"> <div className="input-wrapper">
{Array.isArray(beforeInput) && beforeInput.map((Component, i) => <Component key={i} />)} {Array.isArray(beforeInput) && beforeInput.map((Component, i) => <Component key={i} />)}

View File

@@ -1,8 +1,14 @@
import type { TextField } from 'payload/types' import type { TextField } from 'payload/types'
export type Props = Omit<TextField, 'type'> & { export type FormFieldBase = {
inputRef?: React.MutableRefObject<HTMLInputElement>
onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>
path?: string path?: string
value?: string value?: string
valid?: boolean
errorMessage?: string
} }
export type Props = FormFieldBase &
Omit<TextField, 'type'> & {
inputRef?: React.MutableRefObject<HTMLInputElement>
onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>
}

View File

@@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'
import type { Props } from './types' import type { Props } from './types'
import { textarea } from 'payload/fields/validations'
import { getTranslation } from 'payload/utilities' import { getTranslation } from 'payload/utilities'
import { useConfig } from '../../../providers/Config' import { useConfig } from '../../../providers/Config'
import { useLocale } from '../../../providers/Locale' import { useLocale } from '../../../providers/Locale'
@@ -12,6 +11,7 @@ import useField from '../../useField'
import { isFieldRTL } from '../shared' import { isFieldRTL } from '../shared'
import TextareaInput from './Input' import TextareaInput from './Input'
import './index.scss' import './index.scss'
import { Validate } from 'payload/types'
const Textarea: React.FC<Props> = (props) => { const Textarea: React.FC<Props> = (props) => {
const { const {
@@ -33,7 +33,7 @@ const Textarea: React.FC<Props> = (props) => {
minLength, minLength,
path: pathFromProps, path: pathFromProps,
required, required,
validate = textarea, validate,
} = props } = props
const { i18n } = useTranslation() const { i18n } = useTranslation()
@@ -49,11 +49,13 @@ const Textarea: React.FC<Props> = (props) => {
locale, locale,
localizationConfig: localization || undefined, localizationConfig: localization || undefined,
}) })
const memoizedValidate = useCallback(
const memoizedValidate: Validate = useCallback(
(value, options) => { (value, options) => {
return validate(value, { ...options, maxLength, minLength, required }) if (typeof validate === 'function')
return validate(value, { ...options, maxLength, minLength, required })
}, },
[validate, required, maxLength, minLength], [validate, required],
) )
const { errorMessage, setValue, showError, value } = useField({ const { errorMessage, setValue, showError, value } = useField({

View File

@@ -3,7 +3,6 @@ import React, { useCallback } from 'react'
import type { Props } from './types' import type { Props } from './types'
import { upload } from 'payload/fields/validations'
import { useConfig } from '../../../providers/Config' import { useConfig } from '../../../providers/Config'
import useField from '../../useField' import useField from '../../useField'
import UploadInput from './Input' import UploadInput from './Input'
@@ -20,7 +19,6 @@ const Upload: React.FC<Props> = (props) => {
name, name,
admin: { admin: {
className, className,
condition,
description, description,
readOnly, readOnly,
style, style,
@@ -33,14 +31,14 @@ const Upload: React.FC<Props> = (props) => {
path, path,
relationTo, relationTo,
required, required,
validate = upload, validate,
} = props } = props
const collection = collections.find((coll) => coll.slug === relationTo) const collection = collections.find((coll) => coll.slug === relationTo)
const memoizedValidate = useCallback( const memoizedValidate = useCallback(
(value, options) => { (value, options) => {
return validate(value, { ...options, required }) if (typeof validate === 'function') return validate(value, { ...options, required })
}, },
[validate, required], [validate, required],
) )

View File

@@ -102,9 +102,9 @@ const useField = <T,>(options: Options): FieldType<T> => {
} }
let errorMessage: string | undefined let errorMessage: string | undefined
let valid: boolean | string = false let valid: boolean | string = prevValid.current
const validationResult = const isValid =
typeof validate === 'function' typeof validate === 'function'
? await validate(valueToValidate, { ? await validate(valueToValidate, {
id, id,
@@ -117,11 +117,11 @@ const useField = <T,>(options: Options): FieldType<T> => {
}) })
: true : true
if (typeof validationResult === 'string') { if (typeof isValid === 'string') {
errorMessage = validationResult
valid = false valid = false
} else { errorMessage = isValid
valid = validationResult } else if (typeof isValid === 'boolean') {
valid = isValid
errorMessage = undefined errorMessage = undefined
} }

View File

@@ -298,6 +298,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
setUser, setUser,
token: tokenInMemory, token: tokenInMemory,
user, user,
setPermissions,
}} }}
> >
{children} {children}

View File

@@ -10,4 +10,5 @@ export type AuthContext<T = User> = {
setUser: (user: T) => void setUser: (user: T) => void
token?: string token?: string
user?: T | null user?: T | null
setPermissions: (permissions: Permissions) => void
} }

View File

@@ -2,7 +2,6 @@
import qs from 'qs' import qs from 'qs'
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react' import React, { createContext, useCallback, useContext, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useParams } from 'react-router-dom'
import type { TypeWithTimestamps } from 'payload/dist/collections/config/types' import type { TypeWithTimestamps } from 'payload/dist/collections/config/types'
import type { PaginatedDocs } from 'payload/database' import type { PaginatedDocs } from 'payload/database'
@@ -13,6 +12,7 @@ import { useAuth } from '../Auth'
import { useConfig } from '../Config' import { useConfig } from '../Config'
import { useLocale } from '../Locale' import { useLocale } from '../Locale'
import { usePreferences } from '../Preferences' import { usePreferences } from '../Preferences'
import { useParams } from 'next/navigation'
const Context = createContext({} as ContextType) const Context = createContext({} as ContextType)
@@ -21,12 +21,14 @@ export const useDocumentInfo = (): ContextType => useContext(Context)
export const DocumentInfoProvider: React.FC<Props> = ({ export const DocumentInfoProvider: React.FC<Props> = ({
id: idFromProps, id: idFromProps,
children, children,
collection, collectionSlug,
global, globalSlug,
idFromParams: getIDFromParams, idFromParams: getIDFromParams,
draftsEnabled,
versionsEnabled,
}) => { }) => {
const { id: idFromParams } = useParams<{ id: string }>() const { id: idFromParams } = useParams()
const id = idFromProps || (getIDFromParams ? idFromParams : null) const id = idFromProps || (getIDFromParams ? (idFromParams as string) : null)
const { const {
routes: { api }, routes: { api },
@@ -46,14 +48,14 @@ export const DocumentInfoProvider: React.FC<Props> = ({
let pluralType: 'collections' | 'globals' let pluralType: 'collections' | 'globals'
let preferencesKey: string let preferencesKey: string
if (global) { if (globalSlug) {
slug = global.slug slug = globalSlug
pluralType = 'globals' pluralType = 'globals'
preferencesKey = `global-${slug}` preferencesKey = `global-${slug}`
} }
if (collection) { if (collectionSlug) {
slug = collection.slug slug = collectionSlug
pluralType = 'collections' pluralType = 'collections'
if (id) { if (id) {
@@ -64,8 +66,7 @@ export const DocumentInfoProvider: React.FC<Props> = ({
const getVersions = useCallback(async () => { const getVersions = useCallback(async () => {
let versionFetchURL let versionFetchURL
let publishedFetchURL let publishedFetchURL
let draftsEnabled = false let shouldFetchVersions = versionsEnabled
let shouldFetchVersions = false
let unpublishedVersionJSON = null let unpublishedVersionJSON = null
let versionJSON = null let versionJSON = null
let shouldFetch = true let shouldFetch = true
@@ -100,19 +101,13 @@ export const DocumentInfoProvider: React.FC<Props> = ({
}, },
} }
if (global) { if (globalSlug) {
draftsEnabled = Boolean(global?.versions?.drafts) versionFetchURL = `${baseURL}/globals/${globalSlug}/versions`
shouldFetchVersions = Boolean(global?.versions) publishedFetchURL = `${baseURL}/globals/${globalSlug}?${qs.stringify(publishedVersionParams)}`
versionFetchURL = `${baseURL}/globals/${global.slug}/versions`
publishedFetchURL = `${baseURL}/globals/${global.slug}?${qs.stringify(
publishedVersionParams,
)}`
} }
if (collection) { if (collectionSlug) {
draftsEnabled = Boolean(collection?.versions?.drafts) versionFetchURL = `${baseURL}/${collectionSlug}/versions`
shouldFetchVersions = Boolean(collection?.versions)
versionFetchURL = `${baseURL}/${collection.slug}/versions`
publishedVersionParams.where.and.push({ publishedVersionParams.where.and.push({
id: { id: {
@@ -120,7 +115,7 @@ export const DocumentInfoProvider: React.FC<Props> = ({
}, },
}) })
publishedFetchURL = `${baseURL}/${collection.slug}?${qs.stringify(publishedVersionParams)}` publishedFetchURL = `${baseURL}/${collectionSlug}?${qs.stringify(publishedVersionParams)}`
if (!id) { if (!id) {
shouldFetch = false shouldFetch = false
@@ -144,7 +139,7 @@ export const DocumentInfoProvider: React.FC<Props> = ({
}, },
}).then((res) => res.json()) }).then((res) => res.json())
if (collection) { if (collectionSlug) {
publishedJSON = publishedJSON?.docs?.[0] publishedJSON = publishedJSON?.docs?.[0]
} }
} }
@@ -194,7 +189,7 @@ export const DocumentInfoProvider: React.FC<Props> = ({
setVersions(versionJSON) setVersions(versionJSON)
setUnpublishedVersions(unpublishedVersionJSON) setUnpublishedVersions(unpublishedVersionJSON)
} }
}, [i18n, global, collection, id, baseURL, code]) }, [i18n, globalSlug, collectionSlug, id, baseURL, code, versionsEnabled, draftsEnabled])
const getDocPermissions = React.useCallback(async () => { const getDocPermissions = React.useCallback(async () => {
let docAccessURL: string let docAccessURL: string
@@ -219,7 +214,7 @@ export const DocumentInfoProvider: React.FC<Props> = ({
} else { } else {
// fallback to permissions from the entity type // fallback to permissions from the entity type
// (i.e. create has no id) // (i.e. create has no id)
setDocPermissions(permissions[pluralType][slug]) setDocPermissions(permissions?.[pluralType]?.[slug])
} }
}, [serverURL, api, pluralType, slug, id, permissions, i18n.language, code]) }, [serverURL, api, pluralType, slug, id, permissions, i18n.language, code])
@@ -261,18 +256,19 @@ export const DocumentInfoProvider: React.FC<Props> = ({
const value: ContextType = { const value: ContextType = {
id, id,
collection, collectionSlug,
docPermissions, docPermissions,
getDocPermissions, getDocPermissions,
getDocPreferences, getDocPreferences,
getVersions, getVersions,
global, globalSlug,
preferencesKey, preferencesKey,
publishedDoc, publishedDoc,
setDocFieldPreferences, setDocFieldPreferences,
slug, slug,
unpublishedVersions, unpublishedVersions,
versions, versionsEnabled,
draftsEnabled,
} }
return <Context.Provider value={value}>{children}</Context.Provider> return <Context.Provider value={value}>{children}</Context.Provider>

View File

@@ -11,25 +11,29 @@ export type Version = TypeWithVersion<any>
export type DocumentPermissions = CollectionPermission | GlobalPermission | null export type DocumentPermissions = CollectionPermission | GlobalPermission | null
export type ContextType = { export type ContextType = {
collection?: SanitizedCollectionConfig collectionSlug?: SanitizedCollectionConfig['slug']
docPermissions: DocumentPermissions docPermissions: DocumentPermissions
getDocPermissions: () => Promise<void> getDocPermissions: () => Promise<void>
getDocPreferences: () => Promise<{ [key: string]: unknown }> getDocPreferences: () => Promise<{ [key: string]: unknown }>
getVersions: () => Promise<void> getVersions: () => Promise<void>
global?: SanitizedGlobalConfig globalSlug?: SanitizedGlobalConfig['slug']
id?: number | string id?: number | string
preferencesKey?: string preferencesKey?: string
publishedDoc?: TypeWithID & TypeWithTimestamps & { _status?: string } publishedDoc?: TypeWithID & TypeWithTimestamps & { _status?: string }
setDocFieldPreferences: (field: string, fieldPreferences: { [key: string]: unknown }) => void setDocFieldPreferences: (field: string, fieldPreferences: { [key: string]: unknown }) => void
slug?: string slug?: string
unpublishedVersions?: PaginatedDocs<Version> unpublishedVersions?: PaginatedDocs<Version>
versions?: PaginatedDocs<Version> versionsCount?: PaginatedDocs<Version>
draftsEnabled?: boolean
versionsEnabled?: boolean
} }
export type Props = { export type Props = {
children?: React.ReactNode children?: React.ReactNode
collection?: SanitizedCollectionConfig collectionSlug?: SanitizedCollectionConfig['slug']
global?: SanitizedGlobalConfig globalSlug?: SanitizedGlobalConfig['slug']
id?: number | string id?: number | string
idFromParams?: boolean idFromParams?: boolean
draftsEnabled?: boolean
versionsEnabled?: boolean
} }

View File

@@ -13,7 +13,7 @@ export const DefaultGlobalEdit: React.FC<
fieldTypes: FieldTypes fieldTypes: FieldTypes
} }
> = (props) => { > = (props) => {
const { apiURL, data, fieldTypes, globalConfig, permissions, config } = props const { apiURL, data, fieldTypes, globalConfig, permissions, config, user, state } = props
const { admin: { description } = {}, fields, label } = globalConfig const { admin: { description } = {}, fields, label } = globalConfig
@@ -40,6 +40,9 @@ export const DefaultGlobalEdit: React.FC<
fields={fields} fields={fields}
hasSavePermission={hasSavePermission} hasSavePermission={hasSavePermission}
permissions={permissions} permissions={permissions}
user={user}
state={state}
data={data}
/> />
</React.Fragment> </React.Fragment>
) )

View File

@@ -20,7 +20,7 @@ export const DefaultGlobalView: React.FC<DefaultGlobalViewProps> = (props) => {
// disableRoutes, // disableRoutes,
// fieldTypes, // fieldTypes,
globalConfig, globalConfig,
initialState, state,
// onSave, // onSave,
permissions, permissions,
} = props } = props
@@ -93,7 +93,7 @@ export const DefaultGlobalView: React.FC<DefaultGlobalViewProps> = (props) => {
action={action} action={action}
className={`${baseClass}__form`} className={`${baseClass}__form`}
disabled={!hasSavePermission} disabled={!hasSavePermission}
initialState={initialState} initialState={state}
method="POST" method="POST"
// onSuccess={onSave} // onSuccess={onSave}
> >

View File

@@ -22,7 +22,7 @@ export type CollectionEditViewProps = BaseEditViewProps & {
export type GlobalEditViewProps = BaseEditViewProps & { export type GlobalEditViewProps = BaseEditViewProps & {
config: SanitizedConfig config: SanitizedConfig
globalConfig: SanitizedGlobalConfig globalConfig: SanitizedGlobalConfig
initialState?: Fields state?: Fields
permissions: GlobalPermission | null permissions: GlobalPermission | null
} }