chore(next): ssr field validations (#4700)
This commit is contained in:
@@ -4,7 +4,7 @@ import { DocumentLayout } from '@payloadcms/next/layouts/Document'
|
||||
import configPromise from 'payload-config'
|
||||
|
||||
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}
|
||||
</DocumentLayout>
|
||||
)
|
||||
|
||||
@@ -15,13 +15,11 @@ export const DocumentLayout = async ({
|
||||
config: configPromise,
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
id,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
config: Promise<SanitizedConfig>
|
||||
collectionSlug?: string
|
||||
globalSlug?: string
|
||||
id?: string
|
||||
}) => {
|
||||
const { user, permissions, config } = await initPage(configPromise)
|
||||
|
||||
@@ -36,14 +34,9 @@ export const DocumentLayout = async ({
|
||||
return (
|
||||
<Fragment>
|
||||
<DocumentHeader
|
||||
// apiURL={apiURL}
|
||||
config={config}
|
||||
collectionConfig={collectionConfig}
|
||||
globalConfig={globalConfig}
|
||||
// customHeader={customHeader}
|
||||
// data={data}
|
||||
id={id}
|
||||
// isEditing={isEditing}
|
||||
/>
|
||||
{children}
|
||||
</Fragment>
|
||||
|
||||
@@ -107,7 +107,7 @@ export const Account = async ({
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<HydrateClientUser user={user} />
|
||||
<HydrateClientUser user={user} permissions={permissions} />
|
||||
<RenderCustomComponent
|
||||
CustomComponent={
|
||||
typeof CustomAccountComponent === 'function' ? CustomAccountComponent : undefined
|
||||
|
||||
@@ -13,9 +13,11 @@ import {
|
||||
FormQueryParamsProvider,
|
||||
QueryParamTypes,
|
||||
HydrateClientUser,
|
||||
DocumentInfoProvider,
|
||||
} from '@payloadcms/ui'
|
||||
import queryString from 'qs'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { TFunction } from 'i18next'
|
||||
|
||||
export const CollectionEdit = async ({
|
||||
collectionSlug,
|
||||
@@ -99,7 +101,7 @@ export const CollectionEdit = async ({
|
||||
locale,
|
||||
operation: isEditing ? 'update' : 'create',
|
||||
preferences,
|
||||
// t,
|
||||
t: ((key: string) => key) as TFunction, // TODO: i18n
|
||||
user,
|
||||
})
|
||||
|
||||
@@ -136,7 +138,14 @@ export const CollectionEdit = async ({
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<HydrateClientUser user={user} />
|
||||
<HydrateClientUser user={user} permissions={permissions} />
|
||||
<DocumentInfoProvider
|
||||
collectionSlug={collectionConfig.slug}
|
||||
id={id}
|
||||
key={`${collectionSlug}-${locale}`}
|
||||
versionsEnabled={Boolean(collectionConfig.versions)}
|
||||
draftsEnabled={Boolean(collectionConfig.versions?.drafts)}
|
||||
>
|
||||
<EditDepthProvider depth={1}>
|
||||
<FormQueryParamsProvider formQueryParams={formQueryParams}>
|
||||
<RenderCustomComponent
|
||||
@@ -146,6 +155,7 @@ export const CollectionEdit = async ({
|
||||
/>
|
||||
</FormQueryParamsProvider>
|
||||
</EditDepthProvider>
|
||||
</DocumentInfoProvider>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ export const CollectionList = async ({
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<HydrateClientUser user={user} />
|
||||
<HydrateClientUser user={user} permissions={permissions} />
|
||||
<RenderCustomComponent
|
||||
CustomComponent={ListToRender}
|
||||
DefaultComponent={DefaultList}
|
||||
|
||||
@@ -11,13 +11,13 @@ export const Dashboard = async ({
|
||||
config: Promise<SanitizedConfig>
|
||||
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
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<HydrateClientUser user={user} />
|
||||
<HydrateClientUser user={user} permissions={permissions} />
|
||||
<RenderCustomComponent
|
||||
CustomComponent={
|
||||
typeof CustomDashboardComponent === 'function' ? CustomDashboardComponent : undefined
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
FormQueryParamsProvider,
|
||||
QueryParamTypes,
|
||||
HydrateClientUser,
|
||||
DocumentInfoProvider,
|
||||
} from '@payloadcms/ui'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { Metadata } from 'next'
|
||||
@@ -126,7 +127,7 @@ export const Global = async ({
|
||||
globalConfig,
|
||||
data,
|
||||
fieldTypes,
|
||||
initialState: state,
|
||||
state,
|
||||
permissions: globalPermission,
|
||||
updatedAt: data?.updatedAt?.toString(),
|
||||
user,
|
||||
@@ -135,7 +136,13 @@ export const Global = async ({
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<HydrateClientUser user={user} />
|
||||
<HydrateClientUser user={user} permissions={permissions} />
|
||||
<DocumentInfoProvider
|
||||
collectionSlug={globalConfig.slug}
|
||||
key={`${globalSlug}-${locale}`}
|
||||
versionsEnabled={Boolean(globalConfig.versions)}
|
||||
draftsEnabled={Boolean(globalConfig.versions?.drafts)}
|
||||
>
|
||||
<EditDepthProvider depth={1}>
|
||||
<FormQueryParamsProvider formQueryParams={formQueryParams}>
|
||||
<RenderCustomComponent
|
||||
@@ -145,6 +152,7 @@ export const Global = async ({
|
||||
/>
|
||||
</FormQueryParamsProvider>
|
||||
</EditDepthProvider>
|
||||
</DocumentInfoProvider>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { DefaultVersionsView } from './Default'
|
||||
import { SanitizedConfig } from 'payload/types'
|
||||
import { initPage } from '../../utilities/initPage'
|
||||
import { DefaultVersionsViewProps } from './Default/types'
|
||||
import { notFound } from 'next/navigation'
|
||||
|
||||
export const VersionsView = async ({
|
||||
collectionSlug,
|
||||
@@ -46,6 +47,7 @@ export const VersionsView = async ({
|
||||
let versionsData
|
||||
|
||||
if (collectionSlug) {
|
||||
try {
|
||||
data = await payload.findByID({
|
||||
collection: collectionSlug,
|
||||
id,
|
||||
@@ -53,7 +55,15 @@ export const VersionsView = async ({
|
||||
user,
|
||||
// draft: true,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
try {
|
||||
versionsData = await payload.findVersions({
|
||||
collection: collectionSlug,
|
||||
depth: 0,
|
||||
@@ -68,6 +78,9 @@ export const VersionsView = async ({
|
||||
// },
|
||||
// },
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
docURL = `${serverURL}${api}/${slug}/${id}`
|
||||
// entityLabel = getTranslation(collectionConfig.labels.singular, i18n)
|
||||
@@ -97,13 +110,22 @@ export const VersionsView = async ({
|
||||
}
|
||||
|
||||
if (globalSlug) {
|
||||
try {
|
||||
data = await payload.findGlobal({
|
||||
slug: globalSlug,
|
||||
depth: 0,
|
||||
user,
|
||||
// draft: true,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
try {
|
||||
versionsData = await payload.findGlobalVersions({
|
||||
slug: globalSlug,
|
||||
depth: 0,
|
||||
@@ -116,6 +138,13 @@ export const VersionsView = async ({
|
||||
},
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
if (!versionsData) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
docURL = `${serverURL}${api}/globals/${globalSlug}`
|
||||
// entityLabel = getTranslation(globalConfig.label, i18n)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { Modal, useModal } from '@faceless-ui/modal'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import type { Props } from './types'
|
||||
@@ -11,22 +10,17 @@ import { getTranslation } from 'payload/utilities'
|
||||
// import { requests } from '../../../api'
|
||||
import useTitle from '../../hooks/useTitle'
|
||||
import { useForm } from '../../forms/Form/context'
|
||||
import { Minimal as MinimalTemplate } from '../../templates/Minimal'
|
||||
import { MinimalTemplate } from '../../templates/Minimal'
|
||||
import { useConfig } from '../../providers/Config'
|
||||
import { Button } from '../Button'
|
||||
import * as PopupList from '../Popup/PopupButtonList'
|
||||
import './index.scss'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
const baseClass = 'delete-document'
|
||||
|
||||
const DeleteDocument: React.FC<Props> = (props) => {
|
||||
const {
|
||||
id,
|
||||
buttonId,
|
||||
collection: { labels: { singular } = {}, slug } = {},
|
||||
collection,
|
||||
title: titleFromProps,
|
||||
} = props
|
||||
const { id, buttonId, useAsTitle, collectionSlug, singularLabel, title: titleFromProps } = props
|
||||
|
||||
const {
|
||||
routes: { admin, api },
|
||||
@@ -36,9 +30,12 @@ const DeleteDocument: React.FC<Props> = (props) => {
|
||||
const { setModified } = useForm()
|
||||
const [deleting, setDeleting] = useState(false)
|
||||
const { toggleModal } = useModal()
|
||||
const history = useHistory()
|
||||
const history = useRouter()
|
||||
const { i18n, t } = useTranslation('general')
|
||||
const title = useTitle({ collection })
|
||||
const title = useTitle({
|
||||
useAsTitle,
|
||||
})
|
||||
|
||||
const titleToRender = titleFromProps || title || id
|
||||
|
||||
const modalSlug = `delete-${id}`
|
||||
@@ -86,12 +83,12 @@ const DeleteDocument: React.FC<Props> = (props) => {
|
||||
setModified,
|
||||
serverURL,
|
||||
api,
|
||||
slug,
|
||||
collectionSlug,
|
||||
id,
|
||||
toggleModal,
|
||||
modalSlug,
|
||||
t,
|
||||
singular,
|
||||
singularLabel,
|
||||
i18n,
|
||||
title,
|
||||
history,
|
||||
@@ -118,7 +115,7 @@ const DeleteDocument: React.FC<Props> = (props) => {
|
||||
<Trans
|
||||
i18nKey="aboutToDelete"
|
||||
t={t}
|
||||
values={{ label: getTranslation(singular, i18n), title: titleToRender }}
|
||||
values={{ label: getTranslation(singularLabel, i18n), title: titleToRender }}
|
||||
>
|
||||
aboutToDelete
|
||||
<strong>{titleToRender}</strong>
|
||||
|
||||
@@ -2,7 +2,9 @@ import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
export type Props = {
|
||||
buttonId?: string
|
||||
collection?: SanitizedCollectionConfig
|
||||
useAsTitle: SanitizedCollectionConfig['admin']['useAsTitle']
|
||||
collectionSlug: SanitizedCollectionConfig['slug']
|
||||
singularLabel: SanitizedCollectionConfig['labels']['singular']
|
||||
id?: string
|
||||
title?: string
|
||||
}
|
||||
|
||||
@@ -70,6 +70,10 @@ export const DocumentControls: React.FC<{
|
||||
{collectionConfig && !isEditing && !isAccountView && (
|
||||
<li className={`${baseClass}__list-item`}>
|
||||
<p className={`${baseClass}__value`}>
|
||||
Creating new{' '}
|
||||
{typeof collectionConfig?.labels?.singular === 'string'
|
||||
? collectionConfig.labels.singular
|
||||
: 'Doc'}
|
||||
{/* {t('creatingNewLabel', {
|
||||
label:
|
||||
typeof collectionConfig?.labels?.singular === 'string'
|
||||
@@ -221,25 +225,31 @@ export const DocumentControls: React.FC<{
|
||||
<PopupList.ButtonGroup>
|
||||
{hasCreatePermission && (
|
||||
<React.Fragment>
|
||||
{/* <PopupList.Button
|
||||
<PopupList.Button
|
||||
id="action-create"
|
||||
to={`${adminRoute}/collections/${collectionConfig?.slug}/create`}
|
||||
>
|
||||
{t('createNew')}
|
||||
</PopupList.Button> */}
|
||||
|
||||
{/* {!collectionConfig?.admin?.disableDuplicate && isEditing && (
|
||||
Create New
|
||||
{/* {t('createNew')} */}
|
||||
</PopupList.Button>
|
||||
{!collectionConfig?.admin?.disableDuplicate && isEditing && (
|
||||
<DuplicateDocument
|
||||
collection={collectionConfig}
|
||||
singularLabel={collectionConfig?.labels?.singular}
|
||||
id={id}
|
||||
slug={collectionConfig?.slug}
|
||||
/>
|
||||
)} */}
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
{/* {hasDeletePermission && (
|
||||
<DeleteDocument buttonId="action-delete" collection={collectionConfig} id={id} />
|
||||
)} */}
|
||||
{hasDeletePermission && (
|
||||
<DeleteDocument
|
||||
buttonId="action-delete"
|
||||
collectionSlug={collectionConfig?.slug}
|
||||
useAsTitle={collectionConfig?.admin?.useAsTitle}
|
||||
singularLabel={collectionConfig?.labels?.singular}
|
||||
id={id}
|
||||
/>
|
||||
)}
|
||||
</PopupList.ButtonGroup>
|
||||
</Popup>
|
||||
)}
|
||||
|
||||
@@ -43,7 +43,7 @@ export const DocumentHeader: React.FC<{
|
||||
data={data}
|
||||
isDate={titleFieldConfig?.type === 'date'}
|
||||
dateFormat={
|
||||
'date' in titleFieldConfig?.admin
|
||||
titleFieldConfig && 'date' in titleFieldConfig?.admin
|
||||
? titleFieldConfig?.admin?.date?.displayFormat
|
||||
: undefined
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { Modal, useModal } from '@faceless-ui/modal'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import type { Props } from './types'
|
||||
@@ -10,16 +9,17 @@ import type { Props } from './types'
|
||||
import { getTranslation } from 'payload/utilities'
|
||||
// import { requests } from '../../../api'
|
||||
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 { Button } from '../Button'
|
||||
import * as PopupList from '../Popup/PopupButtonList'
|
||||
import './index.scss'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
const baseClass = 'duplicate'
|
||||
|
||||
const Duplicate: React.FC<Props> = ({ id, collection, slug }) => {
|
||||
const { push } = useHistory()
|
||||
const Duplicate: React.FC<Props> = ({ id, slug, singularLabel }) => {
|
||||
const { push } = useRouter()
|
||||
const modified = useFormModified()
|
||||
const { toggleModal } = useModal()
|
||||
const { setModified } = useForm()
|
||||
@@ -66,13 +66,14 @@ const Duplicate: React.FC<Props> = ({ id, collection, slug }) => {
|
||||
|
||||
// let data = await response.json()
|
||||
|
||||
if (typeof collection.admin.hooks?.beforeDuplicate === 'function') {
|
||||
data = await collection.admin.hooks.beforeDuplicate({
|
||||
collection,
|
||||
data,
|
||||
locale,
|
||||
})
|
||||
}
|
||||
// TODO: convert this into a server action
|
||||
// if (typeof collection.admin.hooks?.beforeDuplicate === 'function') {
|
||||
// data = await collection.admin.hooks.beforeDuplicate({
|
||||
// collection,
|
||||
// data,
|
||||
// locale,
|
||||
// })
|
||||
// }
|
||||
|
||||
if (!duplicateID) {
|
||||
if ('createdAt' in data) delete data.createdAt
|
||||
@@ -133,10 +134,9 @@ const Duplicate: React.FC<Props> = ({ id, collection, slug }) => {
|
||||
return
|
||||
}
|
||||
|
||||
toast.success(
|
||||
t('successfullyDuplicated', { label: getTranslation(collection.labels.singular, i18n) }),
|
||||
{ autoClose: 3000 },
|
||||
)
|
||||
toast.success(t('successfullyDuplicated', { label: getTranslation(singularLabel, i18n) }), {
|
||||
autoClose: 3000,
|
||||
})
|
||||
|
||||
if (localeErrors.length > 0) {
|
||||
toast.error(
|
||||
@@ -151,9 +151,7 @@ const Duplicate: React.FC<Props> = ({ id, collection, slug }) => {
|
||||
setModified(false)
|
||||
|
||||
setTimeout(() => {
|
||||
push({
|
||||
pathname: `${admin}/collections/${slug}/${duplicateID}`,
|
||||
})
|
||||
push(`${admin}/collections/${slug}/${duplicateID}`)
|
||||
}, 10)
|
||||
},
|
||||
[
|
||||
@@ -161,7 +159,6 @@ const Duplicate: React.FC<Props> = ({ id, collection, slug }) => {
|
||||
localization,
|
||||
t,
|
||||
i18n,
|
||||
collection,
|
||||
setModified,
|
||||
toggleModal,
|
||||
modalSlug,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { SanitizedCollectionConfig } from '../../../../collections/config/types'
|
||||
import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
export type Props = {
|
||||
collection: SanitizedCollectionConfig
|
||||
singularLabel: SanitizedCollectionConfig['labels']['singular']
|
||||
id: string
|
||||
slug: string
|
||||
}
|
||||
|
||||
@@ -2,13 +2,18 @@
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import { useAuth } from '../../providers/Auth'
|
||||
import { Permissions, User } from 'payload/auth'
|
||||
|
||||
export const HydrateClientUser: React.FC<{ user: any }> = ({ user }) => {
|
||||
const { setUser } = useAuth()
|
||||
export const HydrateClientUser: React.FC<{ user: User; permissions: Permissions }> = ({
|
||||
user,
|
||||
permissions,
|
||||
}) => {
|
||||
const { setUser, setPermissions } = useAuth()
|
||||
|
||||
useEffect(() => {
|
||||
setUser(user)
|
||||
}, [user])
|
||||
setPermissions(permissions)
|
||||
}, [user, permissions, setUser, setPermissions])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -28,14 +28,18 @@ export const LoginForm: React.FC<{
|
||||
action={`${api}/${userSlug}/login`}
|
||||
className={`${baseClass}__form`}
|
||||
disableSuccessStatus
|
||||
initialData={
|
||||
prefillForm
|
||||
? {
|
||||
email: autoLogin.email,
|
||||
password: autoLogin.password,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
initialState={{
|
||||
email: {
|
||||
initialValue: prefillForm ? autoLogin.email : undefined,
|
||||
value: prefillForm ? autoLogin.email : undefined,
|
||||
valid: true,
|
||||
},
|
||||
password: {
|
||||
initialValue: prefillForm ? autoLogin.password : undefined,
|
||||
value: prefillForm ? autoLogin.password : undefined,
|
||||
valid: true,
|
||||
},
|
||||
}}
|
||||
method="POST"
|
||||
redirect={`${admin}${searchParams?.redirect || ''}`}
|
||||
waitForAutocomplete
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
'use client'
|
||||
import type { LinkProps } from 'react-router-dom'
|
||||
|
||||
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'
|
||||
|
||||
const baseClass = 'popup-button-list'
|
||||
|
||||
export const ButtonGroup: React.FC<{
|
||||
buttonSize?: 'default' | 'small'
|
||||
children: React.ReactNode
|
||||
@@ -31,6 +33,7 @@ type MenuButtonProps = {
|
||||
onClick?: () => void
|
||||
to?: LinkProps['to']
|
||||
}
|
||||
|
||||
export const Button: React.FC<MenuButtonProps> = ({
|
||||
id,
|
||||
active,
|
||||
|
||||
@@ -8,3 +8,4 @@ export { useLocale } from '../providers/Locale'
|
||||
export { useActions } from '../providers/ActionsProvider'
|
||||
export { useAuth } from '../providers/Auth'
|
||||
export { useDocumentInfo } from '../providers/DocumentInfo'
|
||||
export { DocumentInfoProvider } from '../providers/DocumentInfo'
|
||||
|
||||
@@ -45,12 +45,12 @@ export const addFieldStatePromise = async ({
|
||||
user,
|
||||
}: Args): Promise<void> => {
|
||||
if (fieldAffectsData(field)) {
|
||||
const validate = operation === 'update' ? field.validate : undefined
|
||||
|
||||
const fieldState: FormField = {
|
||||
// condition: field.admin?.condition,
|
||||
initialValue: undefined,
|
||||
passesCondition,
|
||||
valid: true,
|
||||
// validate: field.validate,
|
||||
value: undefined,
|
||||
}
|
||||
|
||||
@@ -67,18 +67,18 @@ export const addFieldStatePromise = async ({
|
||||
|
||||
let validationResult: boolean | string = true
|
||||
|
||||
// if (typeof fieldState.validate === 'function') {
|
||||
// validationResult = await fieldState.validate(data?.[field.name], {
|
||||
// ...field,
|
||||
// id,
|
||||
// config,
|
||||
// data: fullData,
|
||||
// operation,
|
||||
// siblingData: data,
|
||||
// t,
|
||||
// user,
|
||||
// })
|
||||
// }
|
||||
if (typeof validate === 'function') {
|
||||
validationResult = await validate(data?.[field.name], {
|
||||
...field,
|
||||
id,
|
||||
config,
|
||||
data: fullData,
|
||||
operation,
|
||||
siblingData: data,
|
||||
t,
|
||||
user,
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof validationResult === 'string') {
|
||||
fieldState.errorMessage = validationResult
|
||||
|
||||
@@ -4,8 +4,6 @@ import equal from 'deep-equal'
|
||||
import type { FieldAction, Fields, FormField } from './types'
|
||||
|
||||
import { deepCopyObject } from 'payload/utilities'
|
||||
import getSiblingData from './getSiblingData'
|
||||
import reduceFieldsToValues from './reduceFieldsToValues'
|
||||
import { flattenRows, separateRows } from './rows'
|
||||
|
||||
/**
|
||||
@@ -42,53 +40,14 @@ export function fieldReducer(state: Fields, action: FieldAction): Fields {
|
||||
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': {
|
||||
const newField = Object.entries(action).reduce(
|
||||
(field, [key, value]) => {
|
||||
if (
|
||||
[
|
||||
'condition',
|
||||
'disableFormData',
|
||||
'errorMessage',
|
||||
'initialValue',
|
||||
'passesCondition',
|
||||
'rows',
|
||||
'valid',
|
||||
'validate',
|
||||
|
||||
@@ -31,7 +31,6 @@ import { useLocale } from '../../providers/Locale'
|
||||
import { useOperation } from '../../providers/OperationProvider'
|
||||
import { WatchFormErrors } from './WatchFormErrors'
|
||||
import { buildFieldSchemaMap } from './buildFieldSchemaMap'
|
||||
import buildInitialState from './buildInitialState'
|
||||
import buildStateFromSchema from './buildStateFromSchema'
|
||||
import {
|
||||
FormContext,
|
||||
@@ -51,7 +50,7 @@ import reduceFieldsToValues from './reduceFieldsToValues'
|
||||
const baseClass = 'form'
|
||||
|
||||
const Form: React.FC<Props> = (props) => {
|
||||
const { id, collection, getDocPreferences, global } = useDocumentInfo()
|
||||
const { id, collectionSlug, getDocPreferences, globalSlug } = useDocumentInfo()
|
||||
|
||||
const {
|
||||
action,
|
||||
@@ -59,9 +58,9 @@ const Form: React.FC<Props> = (props) => {
|
||||
className,
|
||||
disableSuccessStatus,
|
||||
disabled,
|
||||
fields: fieldsFromProps = collection?.fields || global?.fields,
|
||||
fields: fieldsFromProps,
|
||||
// fields: fieldsFromProps = collection?.fields || global?.fields,
|
||||
handleResponse,
|
||||
initialData, // values only, paths are required as key - form should build initial state as convenience
|
||||
initialState, // fully formed initial field state
|
||||
method,
|
||||
onSubmit,
|
||||
@@ -83,17 +82,10 @@ const Form: React.FC<Props> = (props) => {
|
||||
const [modified, setModified] = useState(false)
|
||||
const [processing, setProcessing] = useState(false)
|
||||
const [submitted, setSubmitted] = useState(false)
|
||||
const [formattedInitialData, setFormattedInitialData] = useState(buildInitialState(initialData))
|
||||
|
||||
const formRef = useRef<HTMLFormElement>(null)
|
||||
const contextRef = useRef({} as FormContextType)
|
||||
|
||||
let initialFieldState = {}
|
||||
|
||||
if (formattedInitialData) initialFieldState = formattedInitialData
|
||||
if (initialState) initialFieldState = initialState
|
||||
|
||||
const fieldsReducer = useReducer(fieldReducer, {}, () => initialFieldState)
|
||||
const fieldsReducer = useReducer(fieldReducer, {}, () => initialState)
|
||||
/**
|
||||
* `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.
|
||||
@@ -642,15 +634,6 @@ const Form: React.FC<Props> = (props) => {
|
||||
}
|
||||
}, [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(
|
||||
() => {
|
||||
refreshCookie()
|
||||
|
||||
@@ -2,7 +2,7 @@ import type React from 'react'
|
||||
import type { Dispatch } from 'react'
|
||||
|
||||
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 = {
|
||||
blockType?: string
|
||||
@@ -12,15 +12,14 @@ export type Row = {
|
||||
}
|
||||
|
||||
export type FormField = {
|
||||
// condition?: Condition
|
||||
disableFormData?: boolean
|
||||
errorMessage?: string
|
||||
initialValue: unknown
|
||||
passesCondition?: boolean
|
||||
rows?: Row[]
|
||||
valid: boolean
|
||||
// validate?: Validate
|
||||
value: unknown
|
||||
validate?: Validate
|
||||
}
|
||||
|
||||
export type Fields = {
|
||||
@@ -48,7 +47,6 @@ export type Props = {
|
||||
*/
|
||||
fields?: Field[]
|
||||
handleResponse?: (res: Response) => void
|
||||
initialData?: Data
|
||||
initialState?: Fields
|
||||
log?: boolean
|
||||
method?: 'DELETE' | 'GET' | 'PATCH' | 'POST'
|
||||
|
||||
@@ -56,6 +56,8 @@ const RenderFields: React.FC<Props> = (props) => {
|
||||
permissions: fieldPermissions,
|
||||
data,
|
||||
user,
|
||||
valid: fieldState?.valid,
|
||||
errorMessage: fieldState?.errorMessage,
|
||||
}
|
||||
|
||||
if (field) {
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
import { array } from 'payload/fields/validations'
|
||||
import { getTranslation } from 'payload/utilities'
|
||||
import { scrollToID } from '../../../utilities/scrollToID'
|
||||
import Banner from '../../../elements/Banner'
|
||||
@@ -40,7 +39,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
||||
path: pathFromProps,
|
||||
permissions,
|
||||
required,
|
||||
validate = array,
|
||||
validate,
|
||||
} = props
|
||||
|
||||
const path = pathFromProps || name
|
||||
@@ -93,7 +92,6 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
||||
valid,
|
||||
value,
|
||||
} = useField<number>({
|
||||
condition,
|
||||
hasRows: true,
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
import { blocks as blocksValidator } from 'payload/fields/validations'
|
||||
import { getTranslation } from 'payload/utilities'
|
||||
import { scrollToID } from '../../../utilities/scrollToID'
|
||||
import Banner from '../../../elements/Banner'
|
||||
@@ -34,7 +33,7 @@ const BlocksField: React.FC<Props> = (props) => {
|
||||
|
||||
const {
|
||||
name,
|
||||
admin: { className, condition, description, readOnly },
|
||||
admin: { className, description, readOnly },
|
||||
blocks,
|
||||
fieldTypes,
|
||||
forceRender = false,
|
||||
@@ -47,7 +46,7 @@ const BlocksField: React.FC<Props> = (props) => {
|
||||
path: pathFromProps,
|
||||
permissions,
|
||||
required,
|
||||
validate = blocksValidator,
|
||||
validate,
|
||||
} = props
|
||||
|
||||
const path = pathFromProps || name
|
||||
@@ -92,7 +91,6 @@ const BlocksField: React.FC<Props> = (props) => {
|
||||
valid,
|
||||
value,
|
||||
} = useField<number>({
|
||||
condition,
|
||||
hasRows: true,
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use client'
|
||||
import React, { useCallback } from 'react'
|
||||
import React, { Fragment, useCallback } from 'react'
|
||||
|
||||
import './index.scss'
|
||||
import useField from '../../useField'
|
||||
|
||||
const baseClass = 'checkbox-input'
|
||||
import { Validate } from 'payload/types'
|
||||
import { Check } from '../../../icons/Check'
|
||||
import { Line } from '../../../icons/Line'
|
||||
|
||||
type CheckboxInputProps = {
|
||||
'aria-label'?: string
|
||||
@@ -15,10 +15,12 @@ type CheckboxInputProps = {
|
||||
label?: string
|
||||
name?: string
|
||||
onChange?: (value: boolean) => void
|
||||
partialChecked?: boolean
|
||||
readOnly?: boolean
|
||||
required?: boolean
|
||||
path: string
|
||||
validate?: Validate
|
||||
partialChecked?: boolean
|
||||
iconClassName?: string
|
||||
}
|
||||
|
||||
export const CheckboxInput: React.FC<CheckboxInputProps> = (props) => {
|
||||
@@ -28,24 +30,27 @@ export const CheckboxInput: React.FC<CheckboxInputProps> = (props) => {
|
||||
'aria-label': ariaLabel,
|
||||
checked: checkedFromProps,
|
||||
className,
|
||||
iconClassName,
|
||||
inputRef,
|
||||
onChange: onChangeFromProps,
|
||||
readOnly,
|
||||
required,
|
||||
path,
|
||||
validate,
|
||||
partialChecked,
|
||||
} = props
|
||||
|
||||
// const memoizedValidate = useCallback(
|
||||
// (value, options) => {
|
||||
// return validate(value, { ...options, required })
|
||||
// },
|
||||
// [validate, required],
|
||||
// )
|
||||
const memoizedValidate: Validate = useCallback(
|
||||
(value, options) => {
|
||||
if (typeof validate === 'function') return validate(value, { ...options, required })
|
||||
},
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
const { errorMessage, setValue, showError, value } = useField({
|
||||
const { setValue, value } = useField({
|
||||
// disableFormData,
|
||||
path,
|
||||
// validate: memoizedValidate,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
const onToggle = useCallback(() => {
|
||||
@@ -58,10 +63,11 @@ export const CheckboxInput: React.FC<CheckboxInputProps> = (props) => {
|
||||
const checked = checkedFromProps || Boolean(value)
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<input
|
||||
className={className}
|
||||
aria-label={ariaLabel}
|
||||
defaultChecked={checked}
|
||||
defaultChecked={Boolean(checked)}
|
||||
disabled={readOnly}
|
||||
id={id}
|
||||
name={name}
|
||||
@@ -70,5 +76,14 @@ export const CheckboxInput: React.FC<CheckboxInputProps> = (props) => {
|
||||
type="checkbox"
|
||||
required={required}
|
||||
/>
|
||||
<span
|
||||
className={[iconClassName, !value && partialChecked ? 'check' : 'partial']
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
{value && <Check />}
|
||||
{!value && partialChecked && <Line />}
|
||||
</span>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
30
packages/ui/src/forms/field-types/Checkbox/Wrapper.tsx
Normal file
30
packages/ui/src/forms/field-types/Checkbox/Wrapper.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -2,15 +2,16 @@ import React from 'react'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
import { checkbox } from 'payload/fields/validations'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import { CheckboxInput } from './Input'
|
||||
import DefaultLabel from '../../Label'
|
||||
import './index.scss'
|
||||
import { CheckboxWrapper } from './Wrapper'
|
||||
|
||||
const baseClass = 'checkbox'
|
||||
const inputBaseClass = 'checkbox-input'
|
||||
|
||||
const Checkbox: React.FC<Props> = (props) => {
|
||||
const {
|
||||
@@ -25,10 +26,11 @@ const Checkbox: React.FC<Props> = (props) => {
|
||||
} = {},
|
||||
disableFormData,
|
||||
label,
|
||||
onChange,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
validate = checkbox,
|
||||
valid,
|
||||
errorMessage,
|
||||
value,
|
||||
} = props
|
||||
|
||||
const path = pathFromProps || name
|
||||
@@ -43,9 +45,9 @@ const Checkbox: React.FC<Props> = (props) => {
|
||||
className={[
|
||||
fieldBaseClass,
|
||||
baseClass,
|
||||
// showError && 'error',
|
||||
!valid && 'error',
|
||||
className,
|
||||
// value && `${baseClass}--checked`,
|
||||
value && `${baseClass}--checked`,
|
||||
readOnly && `${baseClass}--read-only`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
@@ -56,23 +58,10 @@ const Checkbox: React.FC<Props> = (props) => {
|
||||
}}
|
||||
>
|
||||
<div className={`${baseClass}__error-wrap`}>
|
||||
<ErrorComp
|
||||
alignCaret="left"
|
||||
// message={errorMessage}
|
||||
// showError={showError}
|
||||
/>
|
||||
<ErrorComp alignCaret="left" message={errorMessage} showError={!valid} />
|
||||
</div>
|
||||
<div
|
||||
className={[
|
||||
baseClass,
|
||||
className,
|
||||
// (checked || partialChecked) && `${baseClass}--checked`,
|
||||
readOnly && `${baseClass}--read-only`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
<div className={`${baseClass}__input`}>
|
||||
<CheckboxWrapper path={path} readOnly={readOnly} baseClass={inputBaseClass}>
|
||||
<div className={`${inputBaseClass}__input`}>
|
||||
{Array.isArray(beforeInput) && beforeInput.map((Component, i) => <Component key={i} />)}
|
||||
<CheckboxInput
|
||||
id={fieldID}
|
||||
@@ -82,20 +71,13 @@ const Checkbox: React.FC<Props> = (props) => {
|
||||
readOnly={readOnly}
|
||||
required={required}
|
||||
path={path}
|
||||
iconClassName={`${inputBaseClass}__icon`}
|
||||
/>
|
||||
{Array.isArray(afterInput) && afterInput.map((Component, i) => <Component key={i} />)}
|
||||
{/* <span className={`${baseClass}__icon ${!partialChecked ? 'check' : 'partial'}`}>
|
||||
{!partialChecked && <Check />}
|
||||
{partialChecked && <Line />}
|
||||
</span> */}
|
||||
</div>
|
||||
{label && <LabelComp htmlFor={fieldID} label={label} required={required} />}
|
||||
</div>
|
||||
<FieldDescription
|
||||
description={description}
|
||||
path={path}
|
||||
// value={value}
|
||||
/>
|
||||
</CheckboxWrapper>
|
||||
<FieldDescription description={description} path={path} value={value} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { CheckboxField } from 'payload/types'
|
||||
import { FormFieldBase } from '../Text/types'
|
||||
|
||||
export type Props = Omit<CheckboxField, 'type'> & {
|
||||
export type Props = FormFieldBase &
|
||||
Omit<CheckboxField, 'type'> & {
|
||||
disableFormData?: boolean
|
||||
onChange?: (val: boolean) => void
|
||||
path?: string
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import React, { useCallback } from 'react'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
import { code } from 'payload/fields/validations'
|
||||
import { CodeEditor } from '../../../elements/CodeEditor'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
@@ -35,7 +34,7 @@ const Code: React.FC<Props> = (props) => {
|
||||
label,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
validate = code,
|
||||
validate,
|
||||
} = props
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import type { DateField } from 'payload/types'
|
||||
import type { DateField, Validate } from 'payload/types'
|
||||
|
||||
import { getTranslation } from 'payload/utilities'
|
||||
import DatePicker from '../../../elements/DatePicker'
|
||||
@@ -18,21 +18,20 @@ export type DateTimeInputProps = Omit<DateField, 'admin' | 'name' | 'type'> & {
|
||||
}
|
||||
|
||||
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 memoizedValidate = useCallback(
|
||||
// (value, options) => {
|
||||
// return validate(value, { ...options, required })
|
||||
// },
|
||||
// [validate, required],
|
||||
// )
|
||||
const memoizedValidate: Validate = useCallback(
|
||||
(value, options) => {
|
||||
if (typeof validate === 'function') return validate(value, { ...options, required })
|
||||
},
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
const { errorMessage, setValue, showError, value } = useField<Date>({
|
||||
// condition,
|
||||
path,
|
||||
// validate: memoizedValidate,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from 'react'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
import { date as dateValidation } from 'payload/fields/validations'
|
||||
import { DateTimeInput } from './Input'
|
||||
import './index.scss'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
@@ -28,7 +27,6 @@ const DateTime: React.FC<Props> = (props) => {
|
||||
label,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
validate = dateValidation,
|
||||
} = props
|
||||
|
||||
const path = pathFromProps || name
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import useField from '../../useField'
|
||||
import './index.scss'
|
||||
import { getTranslation } from 'payload/utilities'
|
||||
import { Validate } from 'payload/types'
|
||||
|
||||
export const EmailInput: React.FC<{
|
||||
name: string
|
||||
@@ -13,6 +14,7 @@ export const EmailInput: React.FC<{
|
||||
path: string
|
||||
required?: boolean
|
||||
placeholder?: Record<string, string> | string
|
||||
validate?: Validate
|
||||
}> = (props) => {
|
||||
const {
|
||||
name,
|
||||
@@ -20,7 +22,7 @@ export const EmailInput: React.FC<{
|
||||
readOnly,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
// validate = email,
|
||||
validate,
|
||||
placeholder,
|
||||
} = props
|
||||
|
||||
@@ -28,12 +30,12 @@ export const EmailInput: React.FC<{
|
||||
|
||||
const path = pathFromProps || name
|
||||
|
||||
// const memoizedValidate = useCallback(
|
||||
// (value, options) => {
|
||||
// return validate(value, { ...options, required })
|
||||
// },
|
||||
// [validate, required],
|
||||
// )
|
||||
const memoizedValidate: Validate = useCallback(
|
||||
(value, options) => {
|
||||
if (typeof validate === 'function') return validate(value, { ...options, required })
|
||||
},
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
const {
|
||||
// errorMessage,
|
||||
@@ -42,7 +44,7 @@ export const EmailInput: React.FC<{
|
||||
value,
|
||||
} = useField({
|
||||
path,
|
||||
// validate: memoizedValidate,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from 'react'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
import { email } from 'payload/fields/validations'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import DefaultLabel from '../../Label'
|
||||
@@ -25,7 +24,6 @@ export const Email: React.FC<Props> = (props) => {
|
||||
label,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
validate = email,
|
||||
} = props
|
||||
|
||||
const path = pathFromProps || name
|
||||
@@ -59,7 +57,6 @@ export const Email: React.FC<Props> = (props) => {
|
||||
<EmailInput
|
||||
name={name}
|
||||
autoComplete={autoComplete}
|
||||
// condition={condition}
|
||||
readOnly={readOnly}
|
||||
path={path}
|
||||
required={required}
|
||||
|
||||
@@ -3,7 +3,6 @@ import React, { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
import { json } from 'payload/fields/validations'
|
||||
import { CodeEditor } from '../../../elements/CodeEditor'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
@@ -11,6 +10,7 @@ import DefaultLabel from '../../Label'
|
||||
import useField from '../../useField'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import './index.scss'
|
||||
import { Validate } from 'payload/types'
|
||||
|
||||
const baseClass = 'json-field'
|
||||
|
||||
@@ -20,7 +20,6 @@ const JSONField: React.FC<Props> = (props) => {
|
||||
admin: {
|
||||
className,
|
||||
components: { Error, Label } = {},
|
||||
condition,
|
||||
description,
|
||||
editorOptions,
|
||||
readOnly,
|
||||
@@ -30,7 +29,7 @@ const JSONField: React.FC<Props> = (props) => {
|
||||
label,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
validate = json,
|
||||
validate,
|
||||
} = props
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
@@ -41,15 +40,15 @@ const JSONField: React.FC<Props> = (props) => {
|
||||
const [jsonError, setJsonError] = useState<string>()
|
||||
const [hasLoadedValue, setHasLoadedValue] = useState(false)
|
||||
|
||||
const memoizedValidate = useCallback(
|
||||
const memoizedValidate: Validate = useCallback(
|
||||
(value, options) => {
|
||||
if (typeof validate === 'function')
|
||||
return validate(value, { ...options, jsonError, required })
|
||||
},
|
||||
[validate, required, jsonError],
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
const { errorMessage, initialValue, setValue, showError, value } = useField<string>({
|
||||
condition,
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { getTranslation } from 'payload/utilities'
|
||||
import useField from '../../useField'
|
||||
import './index.scss'
|
||||
import { Validate } from 'payload/types'
|
||||
|
||||
export const NumberInput: React.FC<{
|
||||
path: string
|
||||
@@ -16,6 +17,7 @@ export const NumberInput: React.FC<{
|
||||
step?: number
|
||||
hasMany?: boolean
|
||||
name?: string
|
||||
validate?: Validate
|
||||
}> = (props) => {
|
||||
const {
|
||||
name,
|
||||
@@ -27,23 +29,23 @@ export const NumberInput: React.FC<{
|
||||
min,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
validate,
|
||||
} = props
|
||||
|
||||
const { i18n, t } = useTranslation()
|
||||
|
||||
const path = pathFromProps || name
|
||||
|
||||
// const memoizedValidate = useCallback(
|
||||
// (value, options) => {
|
||||
// return validate(value, { ...options, max, min, required })
|
||||
// },
|
||||
// [validate, min, max, required],
|
||||
// )
|
||||
const memoizedValidate = useCallback(
|
||||
(value, options) => {
|
||||
return validate(value, { ...options, max, min, required })
|
||||
},
|
||||
[validate, min, max, required],
|
||||
)
|
||||
|
||||
const { errorMessage, setValue, showError, value } = useField<number | number[]>({
|
||||
// condition,
|
||||
path,
|
||||
// validate: memoizedValidate,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
const handleChange = useCallback(
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import React from 'react'
|
||||
|
||||
import type { Option } from '../../../elements/ReactSelect/types'
|
||||
import type { Props } from './types'
|
||||
|
||||
import { number } from 'payload/fields/validations'
|
||||
import { isNumber } from 'payload/utilities'
|
||||
import ReactSelect from '../../../elements/ReactSelect'
|
||||
import DefaultError from '../../Error'
|
||||
@@ -36,7 +34,7 @@ const NumberField: React.FC<Props> = (props) => {
|
||||
minRows,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
validate = number,
|
||||
validate,
|
||||
} = props
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
@@ -44,10 +42,16 @@ const NumberField: React.FC<Props> = (props) => {
|
||||
|
||||
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[]>({
|
||||
condition,
|
||||
path,
|
||||
// validate: memoizedValidate,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
'use client'
|
||||
import React, { useCallback } from 'react'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
import { password } from 'payload/fields/validations'
|
||||
import useField from '../../useField'
|
||||
import './index.scss'
|
||||
import { Validate } from 'payload/types'
|
||||
|
||||
export const PasswordInput: React.FC<{
|
||||
name: string
|
||||
@@ -13,28 +11,22 @@ export const PasswordInput: React.FC<{
|
||||
disabled?: boolean
|
||||
path: string
|
||||
required?: boolean
|
||||
validate?: Validate
|
||||
}> = (props) => {
|
||||
const {
|
||||
name,
|
||||
autoComplete,
|
||||
disabled,
|
||||
path: pathFromProps,
|
||||
// required,
|
||||
} = props
|
||||
const { name, autoComplete, disabled, path: pathFromProps, required, validate } = props
|
||||
|
||||
const path = pathFromProps || name
|
||||
|
||||
// const memoizedValidate = useCallback(
|
||||
// (value, options) => {
|
||||
// const validationResult = validate(value, { ...options, required })
|
||||
// return validationResult
|
||||
// },
|
||||
// [validate, required],
|
||||
// )
|
||||
const memoizedValidate: Validate = useCallback(
|
||||
(value, options) => {
|
||||
if (typeof validate === 'function') return validate(value, { ...options, required })
|
||||
},
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
const { errorMessage, formProcessing, setValue, showError, value } = useField({
|
||||
path,
|
||||
// validate: memoizedValidate,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from 'react'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
import { password } from 'payload/fields/validations'
|
||||
import Error from '../../Error'
|
||||
import Label from '../../Label'
|
||||
import './index.scss'
|
||||
@@ -19,7 +18,6 @@ export const Password: React.FC<Props> = (props) => {
|
||||
path: pathFromProps,
|
||||
required,
|
||||
style,
|
||||
validate = password,
|
||||
width,
|
||||
} = props
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
import { point } from 'payload/fields/validations'
|
||||
import { getTranslation } from 'payload/utilities'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
@@ -12,6 +11,7 @@ import DefaultLabel from '../../Label'
|
||||
import useField from '../../useField'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import './index.scss'
|
||||
import { Validate } from 'payload/types'
|
||||
|
||||
const baseClass = 'point'
|
||||
|
||||
@@ -32,7 +32,7 @@ const PointField: React.FC<Props> = (props) => {
|
||||
label,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
validate = point,
|
||||
validate,
|
||||
} = props
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
@@ -42,9 +42,9 @@ const PointField: React.FC<Props> = (props) => {
|
||||
|
||||
const { i18n, t } = useTranslation('fields')
|
||||
|
||||
const memoizedValidate = useCallback(
|
||||
const memoizedValidate: Validate = useCallback(
|
||||
(value, options) => {
|
||||
return validate(value, { ...options, required })
|
||||
if (typeof validate === 'function') return validate(value, { ...options, required })
|
||||
},
|
||||
[validate, required],
|
||||
)
|
||||
@@ -55,7 +55,6 @@ const PointField: React.FC<Props> = (props) => {
|
||||
showError,
|
||||
value = [null, null],
|
||||
} = useField<[number, number]>({
|
||||
condition,
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
@@ -3,7 +3,6 @@ import React, { useCallback } from 'react'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
import { radio } from 'payload/fields/validations'
|
||||
import useField from '../../useField'
|
||||
import RadioGroupInput from './Input'
|
||||
|
||||
@@ -24,20 +23,20 @@ const RadioGroup: React.FC<Props> = (props) => {
|
||||
options,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
validate = radio,
|
||||
validate,
|
||||
} = props
|
||||
|
||||
const path = pathFromProps || name
|
||||
|
||||
const memoizedValidate = useCallback(
|
||||
(value, validationOptions) => {
|
||||
if (typeof validate === 'function')
|
||||
return validate(value, { ...validationOptions, options, required })
|
||||
},
|
||||
[validate, options, required],
|
||||
)
|
||||
|
||||
const { errorMessage, setValue, showError, value } = useField<string>({
|
||||
condition,
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
@@ -8,7 +8,6 @@ import type { Where } from 'payload/types'
|
||||
import type { DocumentDrawerProps } from '../../../elements/DocumentDrawer/types'
|
||||
import type { FilterOptionsResult, GetResults, Option, Props, Value } from './types'
|
||||
|
||||
import { relationship } from 'payload/fields/validations'
|
||||
import { wordBoundariesRegex } from 'payload/utilities'
|
||||
import { useDebouncedCallback } from '../../../hooks/useDebouncedCallback'
|
||||
import ReactSelect from '../../../elements/ReactSelect'
|
||||
@@ -55,7 +54,7 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
path,
|
||||
relationTo,
|
||||
required,
|
||||
validate = relationship,
|
||||
validate,
|
||||
} = props
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
@@ -94,7 +93,6 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
)
|
||||
|
||||
const { errorMessage, initialValue, setValue, showError, value } = useField<Value | Value[]>({
|
||||
condition,
|
||||
path: pathOrName,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import React, { useCallback } from 'react'
|
||||
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 { getTranslation } from 'payload/utilities'
|
||||
@@ -17,19 +17,22 @@ const SelectInput: React.FC<{
|
||||
isSortable: boolean
|
||||
options: OptionObject[]
|
||||
path: string
|
||||
}> = ({ readOnly, isClearable, hasMany, isSortable, options, path }) => {
|
||||
validate?: Validate
|
||||
required?: boolean
|
||||
}> = ({ readOnly, isClearable, hasMany, isSortable, options, path, validate, required }) => {
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
// const memoizedValidate = useCallback(
|
||||
// (value, validationOptions) => {
|
||||
// return validate(value, { ...validationOptions, hasMany, options, required })
|
||||
// },
|
||||
// [validate, required, hasMany, options],
|
||||
// )
|
||||
const memoizedValidate: Validate = useCallback(
|
||||
(value, validationOptions) => {
|
||||
if (typeof validate === 'function')
|
||||
return validate(value, { ...validationOptions, hasMany, options, required })
|
||||
},
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
const { errorMessage, setValue, showError, value } = useField({
|
||||
path,
|
||||
// validate: memoizedValidate,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
let valueToRender
|
||||
|
||||
@@ -39,7 +39,6 @@ export const Select: React.FC<Props> = (props) => {
|
||||
options,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
// validate = select,
|
||||
} = props
|
||||
|
||||
const path = pathFromProps || name
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import type { SanitizedConfig } from 'payload/types'
|
||||
import type { SanitizedConfig, Validate } from 'payload/types'
|
||||
|
||||
import { getTranslation } from 'payload/utilities'
|
||||
import { isFieldRTL } from '../shared'
|
||||
@@ -22,6 +22,8 @@ export const TextInput: React.FC<{
|
||||
rtl?: boolean
|
||||
maxLength?: number
|
||||
minLength?: number
|
||||
validate?: Validate
|
||||
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void
|
||||
}> = (props) => {
|
||||
const {
|
||||
path,
|
||||
@@ -30,29 +32,30 @@ export const TextInput: React.FC<{
|
||||
localized,
|
||||
localizationConfig,
|
||||
rtl,
|
||||
// maxLength,
|
||||
// minLength,
|
||||
maxLength,
|
||||
minLength,
|
||||
validate,
|
||||
required,
|
||||
onKeyDown,
|
||||
} = props
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
const locale = useLocale()
|
||||
|
||||
const {
|
||||
// errorMessage,
|
||||
setValue,
|
||||
// showError,
|
||||
value,
|
||||
} = useField({
|
||||
const memoizedValidate: Validate = useCallback(
|
||||
(value, options) => {
|
||||
if (typeof validate === 'function')
|
||||
return validate(value, { ...options, maxLength, minLength, required })
|
||||
},
|
||||
[validate, minLength, maxLength, required],
|
||||
)
|
||||
|
||||
const field = useField({
|
||||
path,
|
||||
// validate: memoizedValidate,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
// const memoizedValidate = useCallback(
|
||||
// (value, options) => {
|
||||
// return validate(value, { ...options, maxLength, minLength, required })
|
||||
// },
|
||||
// [validate, minLength, maxLength, required],
|
||||
// )
|
||||
const { setValue, value } = field
|
||||
|
||||
const renderRTL = isFieldRTL({
|
||||
fieldLocalized: localized,
|
||||
@@ -70,7 +73,7 @@ export const TextInput: React.FC<{
|
||||
onChange={(e) => {
|
||||
setValue(e.target.value)
|
||||
}}
|
||||
// onKeyDown={onKeyDown}
|
||||
onKeyDown={onKeyDown}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
// ref={inputRef}
|
||||
type="text"
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from 'react'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
import { text } from 'payload/fields/validations'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import { TextInput } from './Input'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
@@ -28,7 +27,8 @@ const Text: React.FC<Props> = (props) => {
|
||||
minLength,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
validate = text,
|
||||
valid,
|
||||
errorMessage,
|
||||
} = props
|
||||
|
||||
const path = pathFromProps || name
|
||||
@@ -38,12 +38,7 @@ const Text: React.FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
fieldBaseClass,
|
||||
'text',
|
||||
className,
|
||||
// showError && 'error', readOnly && 'read-only'
|
||||
]
|
||||
className={[fieldBaseClass, 'text', className, !valid && 'error', readOnly && 'read-only']
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
style={{
|
||||
@@ -51,10 +46,7 @@ const Text: React.FC<Props> = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<ErrorComp
|
||||
// message={errorMessage}
|
||||
// showError={showError}
|
||||
/>
|
||||
<ErrorComp message={errorMessage} showError={!valid} />
|
||||
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<div className="input-wrapper">
|
||||
{Array.isArray(beforeInput) && beforeInput.map((Component, i) => <Component key={i} />)}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import type { TextField } from 'payload/types'
|
||||
|
||||
export type Props = Omit<TextField, 'type'> & {
|
||||
inputRef?: React.MutableRefObject<HTMLInputElement>
|
||||
onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>
|
||||
export type FormFieldBase = {
|
||||
path?: string
|
||||
value?: string
|
||||
valid?: boolean
|
||||
errorMessage?: string
|
||||
}
|
||||
|
||||
export type Props = FormFieldBase &
|
||||
Omit<TextField, 'type'> & {
|
||||
inputRef?: React.MutableRefObject<HTMLInputElement>
|
||||
onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
import { textarea } from 'payload/fields/validations'
|
||||
import { getTranslation } from 'payload/utilities'
|
||||
import { useConfig } from '../../../providers/Config'
|
||||
import { useLocale } from '../../../providers/Locale'
|
||||
@@ -12,6 +11,7 @@ import useField from '../../useField'
|
||||
import { isFieldRTL } from '../shared'
|
||||
import TextareaInput from './Input'
|
||||
import './index.scss'
|
||||
import { Validate } from 'payload/types'
|
||||
|
||||
const Textarea: React.FC<Props> = (props) => {
|
||||
const {
|
||||
@@ -33,7 +33,7 @@ const Textarea: React.FC<Props> = (props) => {
|
||||
minLength,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
validate = textarea,
|
||||
validate,
|
||||
} = props
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
@@ -49,11 +49,13 @@ const Textarea: React.FC<Props> = (props) => {
|
||||
locale,
|
||||
localizationConfig: localization || undefined,
|
||||
})
|
||||
const memoizedValidate = useCallback(
|
||||
|
||||
const memoizedValidate: Validate = useCallback(
|
||||
(value, options) => {
|
||||
if (typeof validate === 'function')
|
||||
return validate(value, { ...options, maxLength, minLength, required })
|
||||
},
|
||||
[validate, required, maxLength, minLength],
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
const { errorMessage, setValue, showError, value } = useField({
|
||||
|
||||
@@ -3,7 +3,6 @@ import React, { useCallback } from 'react'
|
||||
|
||||
import type { Props } from './types'
|
||||
|
||||
import { upload } from 'payload/fields/validations'
|
||||
import { useConfig } from '../../../providers/Config'
|
||||
import useField from '../../useField'
|
||||
import UploadInput from './Input'
|
||||
@@ -20,7 +19,6 @@ const Upload: React.FC<Props> = (props) => {
|
||||
name,
|
||||
admin: {
|
||||
className,
|
||||
condition,
|
||||
description,
|
||||
readOnly,
|
||||
style,
|
||||
@@ -33,14 +31,14 @@ const Upload: React.FC<Props> = (props) => {
|
||||
path,
|
||||
relationTo,
|
||||
required,
|
||||
validate = upload,
|
||||
validate,
|
||||
} = props
|
||||
|
||||
const collection = collections.find((coll) => coll.slug === relationTo)
|
||||
|
||||
const memoizedValidate = useCallback(
|
||||
(value, options) => {
|
||||
return validate(value, { ...options, required })
|
||||
if (typeof validate === 'function') return validate(value, { ...options, required })
|
||||
},
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
@@ -102,9 +102,9 @@ const useField = <T,>(options: Options): FieldType<T> => {
|
||||
}
|
||||
|
||||
let errorMessage: string | undefined
|
||||
let valid: boolean | string = false
|
||||
let valid: boolean | string = prevValid.current
|
||||
|
||||
const validationResult =
|
||||
const isValid =
|
||||
typeof validate === 'function'
|
||||
? await validate(valueToValidate, {
|
||||
id,
|
||||
@@ -117,11 +117,11 @@ const useField = <T,>(options: Options): FieldType<T> => {
|
||||
})
|
||||
: true
|
||||
|
||||
if (typeof validationResult === 'string') {
|
||||
errorMessage = validationResult
|
||||
if (typeof isValid === 'string') {
|
||||
valid = false
|
||||
} else {
|
||||
valid = validationResult
|
||||
errorMessage = isValid
|
||||
} else if (typeof isValid === 'boolean') {
|
||||
valid = isValid
|
||||
errorMessage = undefined
|
||||
}
|
||||
|
||||
|
||||
@@ -298,6 +298,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
setUser,
|
||||
token: tokenInMemory,
|
||||
user,
|
||||
setPermissions,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -10,4 +10,5 @@ export type AuthContext<T = User> = {
|
||||
setUser: (user: T) => void
|
||||
token?: string
|
||||
user?: T | null
|
||||
setPermissions: (permissions: Permissions) => void
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import qs from 'qs'
|
||||
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import type { TypeWithTimestamps } from 'payload/dist/collections/config/types'
|
||||
import type { PaginatedDocs } from 'payload/database'
|
||||
@@ -13,6 +12,7 @@ import { useAuth } from '../Auth'
|
||||
import { useConfig } from '../Config'
|
||||
import { useLocale } from '../Locale'
|
||||
import { usePreferences } from '../Preferences'
|
||||
import { useParams } from 'next/navigation'
|
||||
|
||||
const Context = createContext({} as ContextType)
|
||||
|
||||
@@ -21,12 +21,14 @@ export const useDocumentInfo = (): ContextType => useContext(Context)
|
||||
export const DocumentInfoProvider: React.FC<Props> = ({
|
||||
id: idFromProps,
|
||||
children,
|
||||
collection,
|
||||
global,
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
idFromParams: getIDFromParams,
|
||||
draftsEnabled,
|
||||
versionsEnabled,
|
||||
}) => {
|
||||
const { id: idFromParams } = useParams<{ id: string }>()
|
||||
const id = idFromProps || (getIDFromParams ? idFromParams : null)
|
||||
const { id: idFromParams } = useParams()
|
||||
const id = idFromProps || (getIDFromParams ? (idFromParams as string) : null)
|
||||
|
||||
const {
|
||||
routes: { api },
|
||||
@@ -46,14 +48,14 @@ export const DocumentInfoProvider: React.FC<Props> = ({
|
||||
let pluralType: 'collections' | 'globals'
|
||||
let preferencesKey: string
|
||||
|
||||
if (global) {
|
||||
slug = global.slug
|
||||
if (globalSlug) {
|
||||
slug = globalSlug
|
||||
pluralType = 'globals'
|
||||
preferencesKey = `global-${slug}`
|
||||
}
|
||||
|
||||
if (collection) {
|
||||
slug = collection.slug
|
||||
if (collectionSlug) {
|
||||
slug = collectionSlug
|
||||
pluralType = 'collections'
|
||||
|
||||
if (id) {
|
||||
@@ -64,8 +66,7 @@ export const DocumentInfoProvider: React.FC<Props> = ({
|
||||
const getVersions = useCallback(async () => {
|
||||
let versionFetchURL
|
||||
let publishedFetchURL
|
||||
let draftsEnabled = false
|
||||
let shouldFetchVersions = false
|
||||
let shouldFetchVersions = versionsEnabled
|
||||
let unpublishedVersionJSON = null
|
||||
let versionJSON = null
|
||||
let shouldFetch = true
|
||||
@@ -100,19 +101,13 @@ export const DocumentInfoProvider: React.FC<Props> = ({
|
||||
},
|
||||
}
|
||||
|
||||
if (global) {
|
||||
draftsEnabled = Boolean(global?.versions?.drafts)
|
||||
shouldFetchVersions = Boolean(global?.versions)
|
||||
versionFetchURL = `${baseURL}/globals/${global.slug}/versions`
|
||||
publishedFetchURL = `${baseURL}/globals/${global.slug}?${qs.stringify(
|
||||
publishedVersionParams,
|
||||
)}`
|
||||
if (globalSlug) {
|
||||
versionFetchURL = `${baseURL}/globals/${globalSlug}/versions`
|
||||
publishedFetchURL = `${baseURL}/globals/${globalSlug}?${qs.stringify(publishedVersionParams)}`
|
||||
}
|
||||
|
||||
if (collection) {
|
||||
draftsEnabled = Boolean(collection?.versions?.drafts)
|
||||
shouldFetchVersions = Boolean(collection?.versions)
|
||||
versionFetchURL = `${baseURL}/${collection.slug}/versions`
|
||||
if (collectionSlug) {
|
||||
versionFetchURL = `${baseURL}/${collectionSlug}/versions`
|
||||
|
||||
publishedVersionParams.where.and.push({
|
||||
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) {
|
||||
shouldFetch = false
|
||||
@@ -144,7 +139,7 @@ export const DocumentInfoProvider: React.FC<Props> = ({
|
||||
},
|
||||
}).then((res) => res.json())
|
||||
|
||||
if (collection) {
|
||||
if (collectionSlug) {
|
||||
publishedJSON = publishedJSON?.docs?.[0]
|
||||
}
|
||||
}
|
||||
@@ -194,7 +189,7 @@ export const DocumentInfoProvider: React.FC<Props> = ({
|
||||
setVersions(versionJSON)
|
||||
setUnpublishedVersions(unpublishedVersionJSON)
|
||||
}
|
||||
}, [i18n, global, collection, id, baseURL, code])
|
||||
}, [i18n, globalSlug, collectionSlug, id, baseURL, code, versionsEnabled, draftsEnabled])
|
||||
|
||||
const getDocPermissions = React.useCallback(async () => {
|
||||
let docAccessURL: string
|
||||
@@ -219,7 +214,7 @@ export const DocumentInfoProvider: React.FC<Props> = ({
|
||||
} else {
|
||||
// fallback to permissions from the entity type
|
||||
// (i.e. create has no id)
|
||||
setDocPermissions(permissions[pluralType][slug])
|
||||
setDocPermissions(permissions?.[pluralType]?.[slug])
|
||||
}
|
||||
}, [serverURL, api, pluralType, slug, id, permissions, i18n.language, code])
|
||||
|
||||
@@ -261,18 +256,19 @@ export const DocumentInfoProvider: React.FC<Props> = ({
|
||||
|
||||
const value: ContextType = {
|
||||
id,
|
||||
collection,
|
||||
collectionSlug,
|
||||
docPermissions,
|
||||
getDocPermissions,
|
||||
getDocPreferences,
|
||||
getVersions,
|
||||
global,
|
||||
globalSlug,
|
||||
preferencesKey,
|
||||
publishedDoc,
|
||||
setDocFieldPreferences,
|
||||
slug,
|
||||
unpublishedVersions,
|
||||
versions,
|
||||
versionsEnabled,
|
||||
draftsEnabled,
|
||||
}
|
||||
|
||||
return <Context.Provider value={value}>{children}</Context.Provider>
|
||||
|
||||
@@ -11,25 +11,29 @@ export type Version = TypeWithVersion<any>
|
||||
export type DocumentPermissions = CollectionPermission | GlobalPermission | null
|
||||
|
||||
export type ContextType = {
|
||||
collection?: SanitizedCollectionConfig
|
||||
collectionSlug?: SanitizedCollectionConfig['slug']
|
||||
docPermissions: DocumentPermissions
|
||||
getDocPermissions: () => Promise<void>
|
||||
getDocPreferences: () => Promise<{ [key: string]: unknown }>
|
||||
getVersions: () => Promise<void>
|
||||
global?: SanitizedGlobalConfig
|
||||
globalSlug?: SanitizedGlobalConfig['slug']
|
||||
id?: number | string
|
||||
preferencesKey?: string
|
||||
publishedDoc?: TypeWithID & TypeWithTimestamps & { _status?: string }
|
||||
setDocFieldPreferences: (field: string, fieldPreferences: { [key: string]: unknown }) => void
|
||||
slug?: string
|
||||
unpublishedVersions?: PaginatedDocs<Version>
|
||||
versions?: PaginatedDocs<Version>
|
||||
versionsCount?: PaginatedDocs<Version>
|
||||
draftsEnabled?: boolean
|
||||
versionsEnabled?: boolean
|
||||
}
|
||||
|
||||
export type Props = {
|
||||
children?: React.ReactNode
|
||||
collection?: SanitizedCollectionConfig
|
||||
global?: SanitizedGlobalConfig
|
||||
collectionSlug?: SanitizedCollectionConfig['slug']
|
||||
globalSlug?: SanitizedGlobalConfig['slug']
|
||||
id?: number | string
|
||||
idFromParams?: boolean
|
||||
draftsEnabled?: boolean
|
||||
versionsEnabled?: boolean
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export const DefaultGlobalEdit: React.FC<
|
||||
fieldTypes: FieldTypes
|
||||
}
|
||||
> = (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
|
||||
|
||||
@@ -40,6 +40,9 @@ export const DefaultGlobalEdit: React.FC<
|
||||
fields={fields}
|
||||
hasSavePermission={hasSavePermission}
|
||||
permissions={permissions}
|
||||
user={user}
|
||||
state={state}
|
||||
data={data}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
|
||||
@@ -20,7 +20,7 @@ export const DefaultGlobalView: React.FC<DefaultGlobalViewProps> = (props) => {
|
||||
// disableRoutes,
|
||||
// fieldTypes,
|
||||
globalConfig,
|
||||
initialState,
|
||||
state,
|
||||
// onSave,
|
||||
permissions,
|
||||
} = props
|
||||
@@ -93,7 +93,7 @@ export const DefaultGlobalView: React.FC<DefaultGlobalViewProps> = (props) => {
|
||||
action={action}
|
||||
className={`${baseClass}__form`}
|
||||
disabled={!hasSavePermission}
|
||||
initialState={initialState}
|
||||
initialState={state}
|
||||
method="POST"
|
||||
// onSuccess={onSave}
|
||||
>
|
||||
|
||||
@@ -22,7 +22,7 @@ export type CollectionEditViewProps = BaseEditViewProps & {
|
||||
export type GlobalEditViewProps = BaseEditViewProps & {
|
||||
config: SanitizedConfig
|
||||
globalConfig: SanitizedGlobalConfig
|
||||
initialState?: Fields
|
||||
state?: Fields
|
||||
permissions: GlobalPermission | null
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user