chore(next): ssr globals view (#4640)

This commit is contained in:
Jacob Fletcher
2023-12-30 10:03:28 -05:00
committed by GitHub
parent 1287412383
commit 9c0994219e
43 changed files with 582 additions and 520 deletions

View File

@@ -0,0 +1,11 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import { Global } from '@payloadcms/next/pages/Global'
import config from 'payload-config'
export default ({ params, searchParams }) =>
Global({
globalSlug: params.global,
searchParams,
config,
})

View File

@@ -0,0 +1,10 @@
import { CollectionConfig } from 'payload/types'
export const Users: CollectionConfig = {
slug: 'users',
auth: true,
admin: {
useAsTitle: 'email',
},
fields: [],
}

View File

@@ -0,0 +1,11 @@
import { GlobalConfig } from 'payload/types'
export const Settings: GlobalConfig = {
slug: 'settings',
fields: [
{
name: 'title',
type: 'text',
},
],
}

View File

@@ -2,6 +2,8 @@ import { mongooseAdapter } from '@payloadcms/db-mongodb'
// import { postgresAdapter } from '@payloadcms/db-postgres'
// import { lexicalEditor } from '@payloadcms/richtext-lexical'
import { buildConfig } from 'payload/config'
import { Users } from './collections/Users'
import { Settings } from './globals/Settings'
export default buildConfig({
db: mongooseAdapter({
@@ -14,6 +16,8 @@ export default buildConfig({
// }),
// editor: lexicalEditor({}),
secret: process.env.PAYLOAD_SECRET,
collections: [Users],
globals: [Settings],
// onInit: async (payload) => {
// await payload.create({
// collection: 'users',

View File

@@ -41,6 +41,8 @@ export const Account = async ({
const collectionConfig = config.collections.find((collection) => collection.slug === userSlug)
if (collectionConfig) {
const { fields } = collectionConfig
let data: TypeWithID & Record<string, unknown>
try {
@@ -54,7 +56,7 @@ export const Account = async ({
return notFound()
}
const fieldSchema = formatFields(collectionConfig, true)
const fieldSchema = formatFields(fields, true)
let preferencesKey: string
@@ -79,7 +81,7 @@ export const Account = async ({
const state = await buildStateFromSchema({
id: user?.id,
config: collectionConfig,
config,
data: data || {},
fieldSchema,
locale,

View File

@@ -4,7 +4,7 @@ import { initPage } from '../../utilities/initPage'
import {
EditDepthProvider,
RenderCustomComponent,
DefaultEdit,
DefaultEditView,
DefaultEditViewProps,
findLocaleFromCode,
fieldTypes,
@@ -45,6 +45,7 @@ export const CollectionEdit = async ({
if (collectionConfig) {
const {
admin: { components: { views: { Edit: CustomEdit } = {} } = {} },
fields,
} = collectionConfig
let data: TypeWithID & Record<string, unknown>
@@ -67,7 +68,7 @@ export const CollectionEdit = async ({
const collectionPermissions = permissions?.collections?.[collectionSlug]
const fieldSchema = formatFields(collectionConfig, isEditing)
const fieldSchema = formatFields(fields, isEditing)
let preferencesKey: string
@@ -92,7 +93,7 @@ export const CollectionEdit = async ({
const state = await buildStateFromSchema({
id,
config: collectionConfig,
config,
data: data || {},
fieldSchema,
locale,
@@ -125,13 +126,12 @@ export const CollectionEdit = async ({
hasSavePermission:
(isEditing && collectionPermissions?.update?.permission) ||
(!isEditing && collectionPermissions?.create?.permission),
internalState: state,
initialState: state,
isEditing,
permissions: collectionPermissions,
updatedAt: data?.updatedAt.toString(),
user,
onSave: () => {},
isLoading: false,
}
return (
@@ -141,7 +141,7 @@ export const CollectionEdit = async ({
<FormQueryParamsProvider formQueryParams={formQueryParams}>
<RenderCustomComponent
CustomComponent={typeof CustomEdit === 'function' ? CustomEdit : undefined}
DefaultComponent={DefaultEdit}
DefaultComponent={DefaultEditView}
componentProps={componentProps}
/>
</FormQueryParamsProvider>

View File

@@ -0,0 +1,153 @@
import { SanitizedConfig, TypeWithID } from 'payload/types'
import React, { Fragment } from 'react'
import { initPage } from '../../utilities/initPage'
import {
EditDepthProvider,
RenderCustomComponent,
DefaultGlobalViewProps,
findLocaleFromCode,
DefaultGlobalView,
fieldTypes,
buildStateFromSchema,
formatFields,
FormQueryParamsProvider,
QueryParamTypes,
HydrateClientUser,
} from '@payloadcms/ui'
import { notFound } from 'next/navigation'
import { Metadata } from 'next'
import { meta } from '../../utilities/meta'
// import i18n from 'i18next'
// import { getTranslation } from 'payload/utilities'
export const generateMetadata = async ({
config,
}: {
config: Promise<SanitizedConfig>
}): Promise<Metadata> =>
meta({
// title: getTranslation(label, i18n),
// description: getTranslation(label, i18n),
// keywords: `${getTranslation(label, i18n)}, Payload, CMS`,
title: '',
description: '',
keywords: '',
config,
})
export const Global = async ({
globalSlug,
config: configPromise,
searchParams,
}: {
globalSlug: string
config: Promise<SanitizedConfig>
searchParams: { [key: string]: string | string[] | undefined }
}) => {
const { config, payload, permissions, user } = await initPage(configPromise, true)
const {
routes: { api },
serverURL,
localization,
} = config
const globalConfig = config.globals.find((global) => global.slug === globalSlug)
if (globalConfig) {
const {
admin: { components: { views: { Edit: CustomEdit } = {} } = {} },
fields,
} = globalConfig
let data: TypeWithID & Record<string, unknown>
try {
data = await payload.findGlobal({
slug: globalSlug,
depth: 0,
user,
})
} catch (error) {}
const defaultLocale =
localization && localization.defaultLocale ? localization.defaultLocale : 'en'
const localeCode = (searchParams?.locale as string) || defaultLocale
const locale = localization && findLocaleFromCode(localization, localeCode)
const globalPermission = permissions?.globals?.[globalSlug]
const fieldSchema = formatFields(fields, true)
const preferencesKey = `global-${globalSlug}`
const {
docs: [preferences],
} = await payload.find({
collection: 'payload-preferences',
depth: 0,
pagination: false,
user,
limit: 1,
where: {
key: {
equals: preferencesKey,
},
},
})
const state = await buildStateFromSchema({
config,
data: data || {},
fieldSchema,
locale,
operation: 'update',
preferences,
// t,
user,
})
const formQueryParams: QueryParamTypes = {
depth: 0,
'fallback-locale': 'null',
locale: '',
uploadEdits: undefined,
}
const componentProps: DefaultGlobalViewProps = {
action: `${serverURL}${api}/globals/${globalSlug}?locale=${locale}&fallback-locale=null`,
apiURL: `${serverURL}${api}/globals/${globalSlug}?locale=${locale}${
global.versions?.drafts ? '&draft=true' : ''
}`,
canAccessAdmin: permissions?.canAccessAdmin,
config,
globalConfig,
data,
fieldTypes,
initialState: state,
permissions: globalPermission,
updatedAt: data?.updatedAt?.toString(),
user,
onSave: () => {},
}
return (
<Fragment>
<HydrateClientUser user={user} />
<EditDepthProvider depth={1}>
<FormQueryParamsProvider formQueryParams={formQueryParams}>
<RenderCustomComponent
CustomComponent={typeof CustomEdit === 'function' ? CustomEdit : undefined}
DefaultComponent={DefaultGlobalView}
componentProps={componentProps}
/>
</FormQueryParamsProvider>
</EditDepthProvider>
</Fragment>
)
}
return notFound()
}

View File

@@ -1,11 +1,11 @@
import React, { useEffect } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import Button from '../../elements/Button'
import { Button } from '../../elements/Button'
import { Gutter } from '../../elements/Gutter'
import { useStepNav } from '../../elements/StepNav'
import { useConfig } from '../../utilities/Config'
import Meta from '../../utilities/Meta'
import { useConfig } from '../../providers/Config'
// import Meta from '../../utilities/Meta'
import './index.scss'
const baseClass = 'not-found'
@@ -21,15 +21,13 @@ const NotFound: React.FC<{
routes: { admin },
} = useConfig()
const { t } = useTranslation('general')
useEffect(() => {
setStepNav([
{
label: t('notFound'),
},
])
}, [setStepNav, t])
// useEffect(() => {
// setStepNav([
// {
// label: t('notFound'),
// },
// ])
// }, [setStepNav, t])
return (
<div
@@ -37,16 +35,23 @@ const NotFound: React.FC<{
.filter(Boolean)
.join(' ')}
>
<Meta
{/* <Meta
description={t('pageNotFound')}
keywords={`404 ${t('notFound')}`}
title={t('notFound')}
/>
/> */}
<Gutter className={`${baseClass}__wrap`}>
<h1>{t('nothingFound')}</h1>
<p>{t('sorryNotFound')}</p>
<h1>
Nothing Found
{/* {t('nothingFound')} */}
</h1>
<p>
Sorry, we couldn't find what you were looking for.
{/* {t('sorryNotFound')} */}
</p>
<Button className={`${baseClass}__button`} el="link" to={`${admin}`}>
{t('backToDashboard')}
Back to Dashboard
{/* {t('backToDashboard')} */}
</Button>
</Gutter>
</div>

View File

@@ -1,96 +0,0 @@
import React, { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import type { FieldTypes } from '../../forms/field-types'
import type { GlobalEditViewProps } from '../types'
import { getTranslation } from '../../../../utilities/getTranslation'
import { DocumentHeader } from '../../elements/DocumentHeader'
import { FormLoadingOverlayToggle } from '../../elements/Loading'
import Form from '../../forms/Form'
import { useActions } from '../../utilities/ActionsProvider'
import { OperationContext } from '../../utilities/OperationProvider'
import { SetStepNav } from '../collections/Edit/SetStepNav'
import { GlobalRoutes } from './Routes'
import { CustomGlobalComponent } from './Routes/CustomComponent'
import './index.scss'
const baseClass = 'global-edit'
export type DefaultGlobalViewProps = GlobalEditViewProps & {
disableRoutes?: boolean
fieldTypes: FieldTypes
}
const DefaultGlobalView: React.FC<DefaultGlobalViewProps> = (props) => {
const { i18n } = useTranslation('general')
const {
action,
apiURL,
data,
disableRoutes,
fieldTypes,
global,
initialState,
isLoading,
onSave,
permissions,
} = props
const { setViewActions } = useActions()
const { label } = global
const hasSavePermission = permissions?.update?.permission
useEffect(() => {
const path = location.pathname
if (!path.endsWith(global.slug)) {
return
}
const editConfig = global?.admin?.components?.views?.Edit
const defaultActions =
editConfig && 'Default' in editConfig && 'actions' in editConfig.Default
? editConfig.Default.actions
: []
setViewActions(defaultActions)
}, [global.slug, location.pathname, global?.admin?.components?.views?.Edit, setViewActions])
return (
<main className={baseClass}>
<OperationContext.Provider value="update">
<SetStepNav global={global} />
<Form
action={action}
className={`${baseClass}__form`}
disabled={!hasSavePermission}
initialState={initialState}
method="post"
onSuccess={onSave}
>
<FormLoadingOverlayToggle
action="update"
loadingSuffix={getTranslation(label, i18n)}
name={`global-edit--${typeof label === 'string' ? label : label?.en}`}
/>
{!isLoading && (
<React.Fragment>
<DocumentHeader apiURL={apiURL} data={data} global={global} />
{disableRoutes ? (
<CustomGlobalComponent view="Default" {...props} />
) : (
<GlobalRoutes {...props} fieldTypes={fieldTypes} />
)}
</React.Fragment>
)}
</Form>
</OperationContext.Provider>
</main>
)
}
export default DefaultGlobalView

View File

@@ -1,154 +0,0 @@
import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useLocation } from 'react-router-dom'
import type { Fields } from '../../forms/Form/types'
import type { DefaultGlobalViewProps } from './Default'
import type { IndexProps } from './types'
import usePayloadAPI from '../../../hooks/usePayloadAPI'
import buildStateFromSchema from '../../forms/Form/buildStateFromSchema'
import { fieldTypes } from '../../forms/field-types'
import { useAuth } from '../../utilities/Auth'
import { useConfig } from '../../utilities/Config'
import { useDocumentEvents } from '../../utilities/DocumentEvents'
import { useDocumentInfo } from '../../utilities/DocumentInfo'
import { EditDepthContext } from '../../utilities/EditDepth'
import { useLocale } from '../../utilities/Locale'
import { usePreferences } from '../../utilities/Preferences'
import RenderCustomComponent from '../../utilities/RenderCustomComponent'
import DefaultGlobalView from './Default'
const GlobalView: React.FC<IndexProps> = (props) => {
const { global } = props
const { state: locationState } = useLocation<{ data?: Record<string, unknown> }>()
const { code: locale } = useLocale()
const { permissions, user } = useAuth()
const [initialState, setInitialState] = useState<Fields>()
const [updatedAt, setUpdatedAt] = useState<string>()
const { docPermissions, getDocPermissions, getDocPreferences, getVersions, preferencesKey } =
useDocumentInfo()
const { getPreference } = usePreferences()
const { t } = useTranslation()
const config = useConfig()
const {
routes: { api },
serverURL,
} = useConfig()
const { reportUpdate } = useDocumentEvents()
const { admin: { components: { views: { Edit: Edit } = {} } = {} } = {}, fields, slug } = global
const onSave = useCallback(
async (json) => {
reportUpdate({
entitySlug: global.slug,
updatedAt: json?.result?.updatedAt || new Date().toISOString(),
})
getVersions()
getDocPermissions()
setUpdatedAt(json?.result?.updatedAt)
const preferences = await getDocPreferences()
const state = await buildStateFromSchema({
config,
data: json.result,
fieldSchema: fields,
locale,
operation: 'update',
preferences,
t,
user,
})
setInitialState(state)
},
[
getVersions,
fields,
user,
locale,
t,
getDocPermissions,
getDocPreferences,
config,
global,
reportUpdate,
],
)
const [{ data, isLoading: isLoadingData }] = usePayloadAPI(`${serverURL}${api}/globals/${slug}`, {
initialData: null,
initialParams: { depth: 0, draft: 'true', 'fallback-locale': 'null' },
})
const dataToRender = locationState?.data || data
useEffect(() => {
const awaitInitialState = async () => {
const preferences = await getDocPreferences()
const state = await buildStateFromSchema({
config,
data: dataToRender,
fieldSchema: fields,
locale,
operation: 'update',
preferences,
t,
user,
})
if (preferencesKey) {
await getPreference(preferencesKey)
}
setInitialState(state)
}
if (dataToRender) awaitInitialState()
}, [
dataToRender,
fields,
user,
locale,
getPreference,
preferencesKey,
t,
getDocPreferences,
config,
])
const isLoading = !initialState || !docPermissions || isLoadingData
const componentProps: DefaultGlobalViewProps = {
action: `${serverURL}${api}/globals/${slug}?locale=${locale}&fallback-locale=null`,
apiURL: `${serverURL}${api}/globals/${slug}?locale=${locale}${
global.versions?.drafts ? '&draft=true' : ''
}`,
canAccessAdmin: permissions?.canAccessAdmin,
data: dataToRender,
fieldTypes,
global,
initialState,
isLoading,
onSave,
permissions: docPermissions,
updatedAt: updatedAt || dataToRender?.updatedAt,
user,
}
return (
<EditDepthContext.Provider value={1}>
<RenderCustomComponent
CustomComponent={typeof Edit === 'function' ? Edit : undefined}
DefaultComponent={DefaultGlobalView}
componentProps={componentProps}
/>
</EditDepthContext.Provider>
)
}
export default GlobalView

View File

@@ -1,5 +0,0 @@
import type { SanitizedGlobalConfig } from '../../../../globals/config/types'
export type IndexProps = {
global: SanitizedGlobalConfig
}

View File

@@ -28,12 +28,12 @@ export const DocumentControls: React.FC<{
apiURL: string
data?: any
disableActions?: boolean
global?: SanitizedGlobalConfig
globalConfig?: SanitizedGlobalConfig
hasSavePermission?: boolean
id?: string
isAccountView?: boolean
isEditing?: boolean
permissions?: CollectionPermission | GlobalPermission | null
permissions: CollectionPermission | GlobalPermission | null
config: SanitizedConfig
collectionConfig?: SanitizedCollectionConfig
}> = (props) => {
@@ -43,7 +43,7 @@ export const DocumentControls: React.FC<{
collectionConfig,
data,
disableActions,
global,
globalConfig,
hasSavePermission,
isAccountView,
isEditing,

View File

@@ -22,7 +22,7 @@ import { useFormQueryParams } from '../../providers/FormQueryParams'
import { useLocale } from '../../providers/Locale'
import { RenderCustomComponent } from '../../elements/RenderCustomComponent'
// import DefaultEdit from '../../views/collections/Edit/Default'
import formatFields from '../../utilities/formatFields'
import { formatFields } from '../../utilities/formatFields'
import { Button } from '../Button'
import IDLabel from '../IDLabel'
@@ -32,10 +32,13 @@ const Content: React.FC<DocumentDrawerProps> = ({
drawerSlug,
onSave,
}) => {
const config = useConfig()
const {
routes: { api },
serverURL,
} = useConfig()
} = config
const { closeModal, modalState, toggleModal } = useModal()
const { code: locale } = useLocale()
const { user } = useAuth()
@@ -44,11 +47,11 @@ const Content: React.FC<DocumentDrawerProps> = ({
const hasInitializedState = useRef(false)
const [isOpen, setIsOpen] = useState(false)
const [collectionConfig] = useRelatedCollections(collectionSlug)
const config = useConfig()
const { formQueryParams } = useFormQueryParams()
const formattedQueryParams = queryString.stringify(formQueryParams)
const { admin: { components: { views: { Edit } = {} } = {} } = {} } = collectionConfig
const { admin: { components: { views: { Edit } = {} } = {} } = {}, fields: fieldsFromConfig } =
collectionConfig
const { id, docPermissions, getDocPreferences } = useDocumentInfo()
@@ -68,7 +71,7 @@ const Content: React.FC<DocumentDrawerProps> = ({
? Edit.Default.Component
: undefined
const [fields, setFields] = useState(() => formatFields(collectionConfig, true))
const [fields, setFields] = useState(() => formatFields(fieldsFromConfig, true))
// no need to an additional requests when creating new documents
const initialID = useRef(id)
@@ -78,7 +81,7 @@ const Content: React.FC<DocumentDrawerProps> = ({
)
useEffect(() => {
setFields(formatFields(collectionConfig, true))
setFields(formatFields(fields, true))
}, [collectionSlug, collectionConfig])
useEffect(() => {

View File

@@ -16,9 +16,9 @@ export const DocumentTab: React.FC<DocumentTabProps & DocumentTabConfig> = (prop
const {
id,
apiURL,
collection,
collectionSlug,
condition,
global,
globalSlug,
href: tabHref,
isActive: checkIsActive,
label,

View File

@@ -1,7 +1,7 @@
import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload/types'
import type { EditViewConfig } from 'payload/config'
import { defaultGlobalViews } from '../../../views/Global/Routes/CustomComponent'
import { defaultGlobalViews } from '../../../views/Global/RenderCustomView'
import { defaultCollectionViews } from '../../../views/collections/Edit/Routes/CustomComponent'
export const getCustomViews = (args: {

View File

@@ -2,30 +2,30 @@ import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload/t
import { EditViewConfig } from 'payload/config'
export const getViewConfig = (args: {
collection: SanitizedCollectionConfig
global: SanitizedGlobalConfig
collectionConfig: SanitizedCollectionConfig
globalConfig: SanitizedGlobalConfig
name: string
}): EditViewConfig => {
const { name, collection, global } = args
const { name, collectionConfig, globalConfig } = args
if (collection) {
const collectionViewsConfig =
typeof collection?.admin?.components?.views?.Edit === 'object' &&
typeof collection?.admin?.components?.views?.Edit !== 'function'
? collection?.admin?.components?.views?.Edit
if (collectionConfig) {
const collectionConfigViewsConfig =
typeof collectionConfig?.admin?.components?.views?.Edit === 'object' &&
typeof collectionConfig?.admin?.components?.views?.Edit !== 'function'
? collectionConfig?.admin?.components?.views?.Edit
: undefined
return collectionViewsConfig?.[name]
return collectionConfigViewsConfig?.[name]
}
if (global) {
const globalViewsConfig =
typeof global?.admin?.components?.views?.Edit === 'object' &&
typeof global?.admin?.components?.views?.Edit !== 'function'
? global?.admin?.components?.views?.Edit
if (globalConfig) {
const globalConfigViewsConfig =
typeof globalConfig?.admin?.components?.views?.Edit === 'object' &&
typeof globalConfig?.admin?.components?.views?.Edit !== 'function'
? globalConfig?.admin?.components?.views?.Edit
: undefined
return globalViewsConfig?.[name]
return globalConfigViewsConfig?.[name]
}
return null

View File

@@ -11,11 +11,11 @@ import { tabs as defaultViews } from './tabs'
const baseClass = 'doc-tabs'
export const DocumentTabs: React.FC<DocumentTabProps> = (props) => {
const { collection, global, isEditing } = props
const { collectionConfig, globalConfig, isEditing } = props
// const customViews = getCustomViews({ collection, global })
// Don't show tabs when creating new documents
if ((collection && isEditing) || global) {
if ((collectionConfig && isEditing) || global) {
return (
<div className={baseClass}>
<div className={`${baseClass}__tabs-container`}>
@@ -31,19 +31,21 @@ export const DocumentTabs: React.FC<DocumentTabProps> = (props) => {
return a.order - b.order
})
?.map(([name, Tab], index) => {
const viewConfig = getViewConfig({ name, collection, global })
const viewConfig = getViewConfig({ name, collectionConfig, globalConfig })
const tabOverrides = viewConfig && 'Tab' in viewConfig ? viewConfig.Tab : undefined
return (
<DocumentTab
{...{
// ...props,
...(Tab || {}),
...(tabOverrides || {}),
}}
key={`tab-${index}`}
/>
)
return null
// return (
// <DocumentTab
// {...{
// // ...props,
// ...(Tab || {}),
// ...(tabOverrides || {}),
// }}
// key={`tab-${index}`}
// />
// )
})}
{/* {customViews?.map((CustomView, index) => {
if ('Tab' in CustomView) {

View File

@@ -8,17 +8,17 @@ import type { ContextType } from '../../../providers/DocumentInfo/types'
export type DocumentTabProps = {
apiURL?: string
collection?: SanitizedCollectionConfig
global?: SanitizedGlobalConfig
collectionConfig?: SanitizedCollectionConfig
globalConfig?: SanitizedGlobalConfig
id: string
isEditing?: boolean
}
export type DocumentTabCondition = (args: {
collection: SanitizedCollectionConfig
collectionConfig: SanitizedCollectionConfig
config: Config
documentInfo: ContextType
global: SanitizedGlobalConfig
globalConfig: SanitizedGlobalConfig
}) => boolean
// Everything is optional because we merge in the defaults

View File

@@ -14,11 +14,11 @@ export const DocumentHeader: React.FC<{
collectionConfig?: SanitizedCollectionConfig
customHeader?: React.ReactNode
data?: any
global?: SanitizedGlobalConfig
globalConfig?: SanitizedGlobalConfig
id?: string
isEditing?: boolean
}> = (props) => {
const { id, apiURL, collectionConfig, customHeader, data, global, isEditing } = props
const { id, apiURL, collectionConfig, customHeader, data, globalConfig, isEditing } = props
return (
<Gutter className={baseClass}>
@@ -28,16 +28,15 @@ export const DocumentHeader: React.FC<{
<RenderTitle
className={`${baseClass}__title`}
useAsTitle={collectionConfig?.admin?.useAsTitle}
globalLabel={global?.label}
globalSlug={global?.slug}
globalLabel={globalConfig?.label}
globalSlug={globalConfig?.slug}
data={data}
// fallback={`[${t('untitled')}]`}
global={global}
/>
<DocumentTabs
apiURL={apiURL}
// collection={collectionConfig}
global={global}
globalConfig={globalConfig}
id={id}
isEditing={isEditing}
/>

View File

@@ -18,7 +18,6 @@ const RenderTitle: React.FC<Props> = (props) => {
data,
element = 'h1',
fallback = '[untitled]',
global,
title: titleFromProps,
} = props

View File

@@ -10,6 +10,5 @@ export type Props = {
}
element?: React.ElementType
fallback?: string
global?: SanitizedGlobalConfig
title?: string
}

View File

@@ -1,2 +1,2 @@
export { findLocaleFromCode } from '../utilities/findLocaleFromCode'
export { default as formatFields } from '../utilities/formatFields'
export { formatFields } from '../utilities/formatFields'

View File

@@ -1,6 +1,8 @@
export { DefaultAccount } from '../views/Account'
export { DefaultDashboard } from '../views/Dashboard'
export { DefaultList } from '../views/List'
export { DefaultEdit } from '../views/Edit'
export { DefaultEditView } from '../views/Edit'
export { DefaultGlobalView } from '../views/Global'
export type { DefaultEditViewProps } from '../views/Edit/types'
export type { DefaultGlobalViewProps } from '../views/Global/types'
export type { DefaultAccountViewProps } from '../views/Account/types'

View File

@@ -4,7 +4,7 @@ import type { TFunction } from 'i18next'
import ObjectID from 'bson-objectid'
import type { User } from 'payload/auth'
import type { NonPresentationalField, SanitizedCollectionConfig } from 'payload/types'
import type { NonPresentationalField, SanitizedConfig } from 'payload/types'
import type { Data, Fields, FormField } from '../types'
import { fieldAffectsData, fieldHasSubFields, tabHasName } from 'payload/types'
@@ -12,7 +12,7 @@ import getValueWithDefault from 'payload/dist/fields/getDefaultValue' // TODO: r
import { iterateFields } from './iterateFields'
type Args = {
config: SanitizedCollectionConfig
config: SanitizedConfig
data: Data
field: NonPresentationalField
fullData: Data

View File

@@ -1,13 +1,13 @@
import type { TFunction } from 'i18next'
import type { User } from 'payload/auth'
import type { Field as FieldSchema, SanitizedCollectionConfig } from 'payload/types'
import type { Field as FieldSchema, SanitizedConfig } from 'payload/types'
import type { Data, Fields } from '../types'
import { iterateFields } from './iterateFields'
type Args = {
config: SanitizedCollectionConfig
config: SanitizedConfig
data?: Data
fieldSchema: FieldSchema[] | undefined
id?: number | string

View File

@@ -1,14 +1,13 @@
import type { TFunction } from 'i18next'
import type { User } from 'payload/auth'
import type { Field as FieldSchema, SanitizedCollectionConfig } from 'payload/types'
import type { Field as FieldSchema, SanitizedConfig } from 'payload/types'
import type { Data, Fields } from '../types'
import { fieldIsPresentationalOnly } from 'payload/types'
import { addFieldStatePromise } from './addFieldStatePromise'
type Args = {
config: SanitizedCollectionConfig
config: SanitizedConfig
data: Data
fields: FieldSchema[]
fullData: Data

View File

@@ -1,106 +1,82 @@
'use client'
import type { ChangeEvent } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import type { TextField } from 'payload/types'
import type { Description } from '../../FieldDescription/types'
import type { SanitizedConfig } from 'payload/types'
import { getTranslation } from 'payload/utilities'
import DefaultError from '../../Error'
import FieldDescription from '../../FieldDescription'
import DefaultLabel from '../../Label'
import { fieldBaseClass } from '../shared'
import { isFieldRTL } from '../shared'
import './index.scss'
import useField from '../../useField'
import { useLocale } from '../../../providers/Locale'
export type TextInputProps = Omit<TextField, 'type'> & {
Error?: React.ComponentType<any>
Label?: React.ComponentType<any>
afterInput?: React.ComponentType<any>[]
beforeInput?: React.ComponentType<any>[]
className?: string
description?: Description
errorMessage?: string
inputRef?: React.MutableRefObject<HTMLInputElement>
onChange?: (e: ChangeEvent<HTMLInputElement>) => void
onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>
path: string
placeholder?: Record<string, string> | string
export const TextInput: React.FC<{
name: string
autoComplete?: string
// condition?: Condition
readOnly?: boolean
path: string
required?: boolean
placeholder?: Record<string, string> | string
localized?: boolean
localizationConfig?: SanitizedConfig['localization']
rtl?: boolean
showError?: boolean
style?: React.CSSProperties
value?: string
width?: string
}
const TextInput: React.FC<TextInputProps> = (props) => {
maxLength?: number
minLength?: number
}> = (props) => {
const {
Error,
Label,
afterInput,
beforeInput,
className,
description,
errorMessage,
inputRef,
label,
onChange,
onKeyDown,
path,
placeholder,
readOnly,
required,
localized,
localizationConfig,
rtl,
showError,
style,
value,
width,
// maxLength,
// minLength,
} = props
const { i18n } = useTranslation()
const locale = useLocale()
const ErrorComp = Error || DefaultError
const LabelComp = Label || DefaultLabel
const {
// errorMessage,
setValue,
// showError,
value,
} = useField({
// condition,
path,
// validate: memoizedValidate,
})
// const memoizedValidate = useCallback(
// (value, options) => {
// return validate(value, { ...options, maxLength, minLength, required })
// },
// [validate, minLength, maxLength, required],
// )
const renderRTL = isFieldRTL({
fieldLocalized: localized,
fieldRTL: rtl,
locale,
localizationConfig: localizationConfig || undefined,
})
return (
<div
className={[fieldBaseClass, 'text', className, showError && 'error', readOnly && 'read-only']
.filter(Boolean)
.join(' ')}
style={{
...style,
width,
<input
data-rtl={renderRTL}
disabled={readOnly}
id={`field-${path.replace(/\./g, '__')}`}
name={path}
onChange={(e) => {
setValue(e.target.value)
}}
>
<ErrorComp message={errorMessage} showError={showError} />
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
<div className="input-wrapper">
{Array.isArray(beforeInput) && beforeInput.map((Component, i) => <Component key={i} />)}
<input
data-rtl={rtl}
disabled={readOnly}
id={`field-${path.replace(/\./g, '__')}`}
name={path}
onChange={onChange}
onKeyDown={onKeyDown}
placeholder={getTranslation(placeholder, i18n)}
ref={inputRef}
type="text"
value={value || ''}
/>
{Array.isArray(afterInput) && afterInput.map((Component, i) => <Component key={i} />)}
</div>
<FieldDescription
className={`field-description-${path.replace(/\./g, '__')}`}
description={description}
path={path}
value={value}
/>
</div>
// onKeyDown={onKeyDown}
placeholder={getTranslation(placeholder, i18n)}
// ref={inputRef}
type="text"
value={(value as string) || ''}
/>
)
}
export default TextInput

View File

@@ -1,13 +1,13 @@
import React, { useCallback } from 'react'
import React from 'react'
import type { Props } from './types'
import { text } from 'payload/fields/validations'
import { useConfig } from '../../../providers/Config'
import { useLocale } from '../../../providers/Locale'
import useField from '../../useField'
import { isFieldRTL } from '../shared'
import TextInput from './Input'
import { fieldBaseClass, isFieldRTL } from '../shared'
import { TextInput } from './Input'
import FieldDescription from '../../FieldDescription'
import DefaultError from '../../Error'
import DefaultLabel from '../../Label'
const Text: React.FC<Props> = (props) => {
const {
@@ -15,7 +15,7 @@ const Text: React.FC<Props> = (props) => {
admin: {
className,
components: { Error, Label, afterInput, beforeInput } = {},
condition,
// condition,
description,
placeholder,
readOnly,
@@ -23,7 +23,7 @@ const Text: React.FC<Props> = (props) => {
style,
width,
} = {},
inputRef,
// inputRef,
label,
localized,
maxLength,
@@ -34,30 +34,52 @@ const Text: React.FC<Props> = (props) => {
} = props
const path = pathFromProps || name
const locale = useLocale()
const { localization } = useConfig()
const isRTL = isFieldRTL({
fieldLocalized: localized,
fieldRTL: rtl,
locale,
localizationConfig: localization || undefined,
})
const ErrorComp = Error || DefaultError
const LabelComp = Label || DefaultLabel
const memoizedValidate = useCallback(
(value, options) => {
return validate(value, { ...options, maxLength, minLength, required })
},
[validate, minLength, maxLength, required],
return (
<div
className={[
fieldBaseClass,
'text',
className,
// showError && 'error', readOnly && 'read-only'
]
.filter(Boolean)
.join(' ')}
style={{
...style,
width,
}}
>
<ErrorComp
// message={errorMessage}
// showError={showError}
/>
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
<div className="input-wrapper">
{Array.isArray(beforeInput) && beforeInput.map((Component, i) => <Component key={i} />)}
<TextInput
path={path}
name={name}
localized={localized}
rtl={rtl}
placeholder={placeholder}
readOnly={readOnly}
maxLength={maxLength}
minLength={minLength}
/>
{Array.isArray(afterInput) && afterInput.map((Component, i) => <Component key={i} />)}
</div>
<FieldDescription
className={`field-description-${path.replace(/\./g, '__')}`}
description={description}
path={path}
// value={value}
/>
</div>
)
const { errorMessage, setValue, showError, value } = useField<string>({
condition,
path,
validate: memoizedValidate,
})
return null
}
export default Text

View File

@@ -1,11 +1,8 @@
import type { SanitizedCollectionConfig } from 'payload/types'
import type { Field } from 'payload/types'
import { fieldAffectsData } from 'payload/types'
const formatFields = (collection: SanitizedCollectionConfig, isEditing?: boolean): Field[] =>
export const formatFields = (fields: Field[], isEditing?: boolean): Field[] =>
isEditing
? collection.fields.filter((field) => (fieldAffectsData(field) && field.name !== 'id') || true)
: collection.fields
export default formatFields
? fields.filter((field) => (fieldAffectsData(field) && field.name !== 'id') || true)
: fields

View File

@@ -30,7 +30,6 @@ export const DefaultCollectionEdit: React.FC<
disableLeaveWithoutSaving,
fieldTypes,
hasSavePermission,
internalState,
isEditing,
permissions,
} = props
@@ -58,7 +57,7 @@ export const DefaultCollectionEdit: React.FC<
collectionSlug={collectionConfig?.slug}
useAsTitle={collectionConfig?.admin?.useAsTitle}
id={id}
isEditing={isEditing}
isEditing={isEditing || false}
pluralLabel={collectionConfig?.labels?.plural}
/>
<DocumentControls

View File

@@ -7,7 +7,6 @@ import type { CollectionEditViewProps } from '../types'
// import VersionView from '../../../Version/Version'
// import VersionsView from '../../../Versions'
import { DefaultCollectionEdit } from './Default/index'
import { SanitizedCollectionConfig } from 'payload/types'
export type collectionViewType =
| 'API'

View File

@@ -18,10 +18,19 @@ export const SetStepNav: React.FC<{
globalSlug?: SanitizedGlobalConfig['slug']
pluralLabel?: SanitizedCollectionConfig['labels']['plural']
id?: number | string
isEditing: boolean
isEditing?: boolean
view?: string
}> = (props) => {
const { collectionSlug, globalSlug, pluralLabel, useAsTitle, id, isEditing, globalLabel } = props
const {
collectionSlug,
globalSlug,
pluralLabel,
useAsTitle,
id,
isEditing = true,
globalLabel,
} = props
const view: string | undefined = props?.view || undefined
const title = useTitle({

View File

@@ -1,4 +1,4 @@
import React from 'react'
import React, { Fragment } from 'react'
import type { DefaultEditViewProps } from './types'
@@ -11,7 +11,7 @@ import { RenderCustomView } from './RenderCustomView'
const baseClass = 'collection-edit'
export const DefaultEdit: React.FC<DefaultEditViewProps> = async (props) => {
export const DefaultEditView: React.FC<DefaultEditViewProps> = async (props) => {
const {
id,
action,
@@ -20,10 +20,10 @@ export const DefaultEdit: React.FC<DefaultEditViewProps> = async (props) => {
customHeader,
data,
hasSavePermission,
internalState,
initialState,
isEditing,
isLoading,
onSave: onSaveFromProps,
// isLoading,
// onSave: onSaveFromProps,
} = props
// const { auth } = collectionConfig
@@ -75,13 +75,13 @@ export const DefaultEdit: React.FC<DefaultEditViewProps> = async (props) => {
action={action}
className={`${baseClass}__form`}
disabled={!hasSavePermission}
initialState={internalState}
initialState={initialState}
method={id ? 'PATCH' : 'POST'}
// onSuccess={onSave}
>
<FormLoadingOverlayToggle
action={isLoading ? 'loading' : operation}
formIsLoading={isLoading}
action={operation}
// formIsLoading={isLoading}
// loadingSuffix={getTranslation(collectionConfig.labels.singular, i18n)}
name={`collection-edit--${
typeof collectionConfig?.labels?.singular === 'string'
@@ -90,19 +90,17 @@ export const DefaultEdit: React.FC<DefaultEditViewProps> = async (props) => {
}`}
type="withoutNav"
/>
{!isLoading && (
<React.Fragment>
<DocumentHeader
apiURL={apiURL}
collectionConfig={collectionConfig}
customHeader={customHeader}
data={data}
id={id}
isEditing={isEditing}
/>
<RenderCustomView view="Default" {...props} />
</React.Fragment>
)}
<Fragment>
<DocumentHeader
apiURL={apiURL}
collectionConfig={collectionConfig}
customHeader={customHeader}
data={data}
id={id}
isEditing={isEditing}
/>
<RenderCustomView {...props} view="Default" />
</Fragment>
</Form>
</OperationProvider>
</main>

View File

@@ -1,41 +1,35 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
import type { FieldTypes } from '../../../forms/field-types'
import type { GlobalEditViewProps } from '../../types'
import { getTranslation } from '../../../../../utilities/getTranslation'
import { DocumentControls } from '../../../elements/DocumentControls'
import { DocumentFields } from '../../../elements/DocumentFields'
import { LeaveWithoutSaving } from '../../../modals/LeaveWithoutSaving'
import Meta from '../../../utilities/Meta'
import { SetStepNav } from '../../collections/Edit/SetStepNav'
import { LeaveWithoutSaving } from '../../../elements/LeaveWithoutSaving'
import { SetStepNav } from '../../Edit/SetStepNav'
export const DefaultGlobalEdit: React.FC<
GlobalEditViewProps & {
fieldTypes: FieldTypes
}
> = (props) => {
const { apiURL, data, fieldTypes, global, permissions } = props
const { i18n } = useTranslation()
const { apiURL, data, fieldTypes, globalConfig, permissions, config } = props
const { admin: { description } = {}, fields, label } = global
const { admin: { description } = {}, fields, label } = globalConfig
const hasSavePermission = permissions?.update?.permission
return (
<React.Fragment>
<Meta
description={getTranslation(label, i18n)}
keywords={`${getTranslation(label, i18n)}, Payload, CMS`}
title={getTranslation(label, i18n)}
/>
{!(global.versions?.drafts && global.versions?.drafts?.autosave) && <LeaveWithoutSaving />}
<SetStepNav global={global} />
{!(globalConfig.versions?.drafts && globalConfig.versions?.drafts?.autosave) && (
<LeaveWithoutSaving />
)}
<SetStepNav globalSlug={globalConfig.slug} globalLabel={label} />
<DocumentControls
apiURL={apiURL}
data={data}
global={global}
config={config}
globalConfig={globalConfig}
hasSavePermission={hasSavePermission}
isEditing
permissions={permissions}

View File

@@ -1,12 +1,8 @@
import React from 'react'
import type { GlobalEditViewProps } from '../../types'
import type { GlobalEditViewProps } from '../types'
import { API } from '../../API'
import { LivePreviewView } from '../../LivePreview'
import VersionView from '../../Version/Version'
import VersionsView from '../../Versions'
import { DefaultGlobalEdit } from '../Default/index'
import { DefaultGlobalEdit } from './Default/index'
export type globalViewType =
| 'API'
@@ -20,23 +16,23 @@ export type globalViewType =
export const defaultGlobalViews: {
[key in globalViewType]: React.ComponentType<any>
} = {
API,
API: null,
Default: DefaultGlobalEdit,
LivePreview: LivePreviewView,
LivePreview: null,
References: null,
Relationships: null,
Version: VersionView,
Versions: VersionsView,
Version: null,
Versions: null,
}
export const CustomGlobalComponent = (
export const RenderCustomView = (
args: GlobalEditViewProps & {
view: globalViewType
},
) => {
const { global, view } = args
const { globalConfig, view } = args
const { admin: { components: { views: { Edit } = {} } = {} } = {} } = global
const { admin: { components: { views: { Edit } = {} } = {} } = {} } = globalConfig
// Overriding components may come from multiple places in the config
// Need to cascade through the hierarchy to find the correct component to render

View File

@@ -6,7 +6,7 @@ import { Route } from 'react-router-dom'
import type { GlobalPermission, User } from '../../../../../auth'
import type { EditView } from '../../../../../config/types'
import type { SanitizedGlobalConfig } from '../../../../../exports/types'
import type { globalViewType } from './CustomComponent'
import type { globalViewType } from '../RenderCustomView'
import Unauthorized from '../../Unauthorized'

View File

@@ -8,7 +8,7 @@ import type { GlobalEditViewProps } from '../../types'
import { useAuth } from '../../../utilities/Auth'
import { useConfig } from '../../../utilities/Config'
import NotFound from '../../NotFound'
import { CustomGlobalComponent } from './CustomComponent'
import { CustomGlobalComponent } from '../RenderCustomView'
import { globalCustomRoutes } from './custom'
// @ts-expect-error Just TypeScript being broken // TODO: Open TypeScript issue

View File

@@ -1,4 +1,4 @@
@import '../../../scss/styles.scss';
@import '../../scss/styles.scss';
.collection-edit {
width: 100%;

View File

@@ -0,0 +1,114 @@
import React from 'react'
import { DocumentHeader } from '../../elements/DocumentHeader'
import { FormLoadingOverlayToggle } from '../../elements/Loading'
import Form from '../../forms/Form'
// import { useActions } from '../../providers/ActionsProvider'
import { OperationProvider } from '../../providers/OperationProvider'
import './index.scss'
import { RenderCustomView } from './RenderCustomView'
import { DefaultGlobalViewProps } from './types'
const baseClass = 'global-edit'
export const DefaultGlobalView: React.FC<DefaultGlobalViewProps> = (props) => {
// const { i18n } = useTranslation('general')
const {
action,
apiURL,
data,
// disableRoutes,
// fieldTypes,
globalConfig,
initialState,
// onSave,
permissions,
} = props
// const { setViewActions } = useActions()
const { label } = globalConfig
// const onSave = useCallback(
// async (json) => {
// reportUpdate({
// entitySlug: global.slug,
// updatedAt: json?.result?.updatedAt || new Date().toISOString(),
// })
// getVersions()
// getDocPermissions()
// setUpdatedAt(json?.result?.updatedAt)
// const preferences = await getDocPreferences()
// const state = await buildStateFromSchema({
// config,
// data: json.result,
// fieldSchema: fields,
// locale,
// operation: 'update',
// preferences,
// t,
// user,
// })
// setInitialState(state)
// },
// [
// getVersions,
// fields,
// user,
// locale,
// t,
// getDocPermissions,
// getDocPreferences,
// config,
// global,
// reportUpdate,
// ],
// )
const hasSavePermission = permissions?.update?.permission
// useEffect(() => {
// const path = location.pathname
// if (!path.endsWith(global.slug)) {
// return
// }
// const editConfig = global?.admin?.components?.views?.Edit
// const defaultActions =
// editConfig && 'Default' in editConfig && 'actions' in editConfig.Default
// ? editConfig.Default.actions
// : []
// setViewActions(defaultActions)
// }, [global.slug, location.pathname, global?.admin?.components?.views?.Edit, setViewActions])
return (
<main className={baseClass}>
<OperationProvider operation="update">
<Form
action={action}
className={`${baseClass}__form`}
disabled={!hasSavePermission}
initialState={initialState}
method="POST"
// onSuccess={onSave}
>
<FormLoadingOverlayToggle
action="update"
// loadingSuffix={getTranslation(label, i18n)}
name={`global-edit--${typeof label === 'string' ? label : label?.en}`}
/>
<React.Fragment>
<DocumentHeader apiURL={apiURL} data={data} globalConfig={globalConfig} />
<RenderCustomView {...props} view="Default" />
</React.Fragment>
</Form>
</OperationProvider>
</main>
)
}

View File

@@ -0,0 +1,12 @@
import type { SanitizedGlobalConfig } from 'payload/types'
import { GlobalEditViewProps } from '../types'
import { FieldTypes } from '../../forms/field-types'
export type IndexProps = {
global: SanitizedGlobalConfig
}
export type DefaultGlobalViewProps = GlobalEditViewProps & {
disableRoutes?: boolean
fieldTypes: FieldTypes
}

View File

@@ -64,6 +64,8 @@ export const DefaultList: React.FC<Props> = (props) => {
})
}
console.log(pluralLabel)
return (
<div className={baseClass}>
<SetStepNav
@@ -82,7 +84,7 @@ export const DefaultList: React.FC<Props> = (props) => {
{customHeader && customHeader}
{!customHeader && (
<Fragment>
<h1>{pluralLabel['en']}</h1>
<h1>{typeof pluralLabel === 'string' ? pluralLabel : pluralLabel['en']}</h1>
{/* <h1>{getTranslation(pluralLabel, i18n)}</h1> */}
{hasCreatePermission && (
<Pill

View File

@@ -14,13 +14,13 @@ export type CollectionEditViewProps = BaseEditViewProps & {
hasSavePermission?: boolean
id: string
initialState?: Fields
internalState?: Fields
isEditing?: boolean
permissions: CollectionPermission | null
}
export type GlobalEditViewProps = BaseEditViewProps & {
global: SanitizedGlobalConfig
config: SanitizedConfig
globalConfig: SanitizedGlobalConfig
initialState?: Fields
permissions: GlobalPermission | null
}
@@ -30,7 +30,7 @@ export type BaseEditViewProps = {
apiURL: string
canAccessAdmin?: boolean
data: any
isLoading: boolean
// isLoading: boolean
onSave: (json: any) => void
updatedAt: string
user: User | null | undefined