chore: improves edit view types (#3427)

This commit is contained in:
Jacob Fletcher
2023-10-03 15:41:15 -04:00
committed by GitHub
parent fdbb61fc43
commit cbc1f3b3f1
22 changed files with 709 additions and 746 deletions

View File

@@ -2,6 +2,7 @@ import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import type { Translation } from '../../../../translations/type' import type { Translation } from '../../../../translations/type'
import type { CollectionEditViewProps } from '../types'
import { DocumentControls } from '../../elements/DocumentControls' import { DocumentControls } from '../../elements/DocumentControls'
import { DocumentHeader } from '../../elements/DocumentHeader' import { DocumentHeader } from '../../elements/DocumentHeader'
@@ -19,134 +20,127 @@ import { OperationContext } from '../../utilities/OperationProvider'
import Auth from '../collections/Edit/Auth' import Auth from '../collections/Edit/Auth'
import { ToggleTheme } from './ToggleTheme' import { ToggleTheme } from './ToggleTheme'
import './index.scss' import './index.scss'
import { EditViewProps } from '../types'
const baseClass = 'account' const baseClass = 'account'
const DefaultAccount: React.FC<EditViewProps> = (props) => { const DefaultAccount: React.FC<CollectionEditViewProps> = (props) => {
if ('collection' in props) { const {
const { action,
action, apiURL,
apiURL, collection,
collection, data,
data, hasSavePermission,
hasSavePermission, initialState,
initialState, isLoading,
isLoading, onSave: onSaveFromProps,
onSave: onSaveFromProps, permissions,
permissions, } = props
} = props
const { auth, fields } = collection const { auth, fields } = collection
const { refreshCookieAsync } = useAuth() const { refreshCookieAsync } = useAuth()
const { i18n, t } = useTranslation('authentication') const { i18n, t } = useTranslation('authentication')
const languageOptions = Object.entries(i18n.options.resources).map(([language, resource]) => ({ const languageOptions = Object.entries(i18n.options.resources).map(([language, resource]) => ({
label: (resource as Translation).general.thisLanguage, label: (resource as Translation).general.thisLanguage,
value: language, value: language,
})) }))
const onSave = useCallback(async () => { const onSave = useCallback(async () => {
await refreshCookieAsync() await refreshCookieAsync()
if (typeof onSaveFromProps === 'function') { if (typeof onSaveFromProps === 'function') {
onSaveFromProps({}) onSaveFromProps({})
} }
}, [onSaveFromProps, refreshCookieAsync]) }, [onSaveFromProps, refreshCookieAsync])
const classes = [baseClass].filter(Boolean).join(' ') const classes = [baseClass].filter(Boolean).join(' ')
return ( return (
<React.Fragment> <React.Fragment>
<LoadingOverlayToggle name="account" show={isLoading} type="withoutNav" /> <LoadingOverlayToggle name="account" show={isLoading} type="withoutNav" />
{!isLoading && ( {!isLoading && (
<div className={classes}> <div className={classes}>
<OperationContext.Provider value="update"> <OperationContext.Provider value="update">
<Form <Form
action={action} action={action}
className={`${baseClass}__form`} className={`${baseClass}__form`}
disabled={!hasSavePermission} disabled={!hasSavePermission}
initialState={initialState} initialState={initialState}
method="patch" method="patch"
onSuccess={onSave} onSuccess={onSave}
> >
<DocumentHeader apiURL={apiURL} collection={collection} data={data} /> <DocumentHeader apiURL={apiURL} collection={collection} data={data} />
<DocumentControls <DocumentControls
apiURL={apiURL} apiURL={apiURL}
collection={collection} collection={collection}
data={data} data={data}
hasSavePermission={hasSavePermission} hasSavePermission={hasSavePermission}
permissions={permissions} isAccountView
isAccountView permissions={permissions}
/>
<div className={`${baseClass}__main`}>
<Meta
description={t('accountOfCurrentUser')}
keywords={t('account')}
title={t('account')}
/> />
<div className={`${baseClass}__main`}> {!(collection.versions?.drafts && collection.versions?.drafts?.autosave) && (
<Meta <LeaveWithoutSaving />
description={t('accountOfCurrentUser')} )}
keywords={t('account')} <div className={`${baseClass}__edit`}>
title={t('account')} <Gutter className={`${baseClass}__header`}>
/> <Auth
{!(collection.versions?.drafts && collection.versions?.drafts?.autosave) && ( className={`${baseClass}__auth`}
<LeaveWithoutSaving /> collection={collection}
)} email={data?.email}
<div className={`${baseClass}__edit`}> operation="update"
<Gutter className={`${baseClass}__header`}> readOnly={!hasSavePermission}
<Auth useAPIKey={auth.useAPIKey}
collection={collection} />
email={data?.email} <RenderFields
operation="update" fieldSchema={fields}
readOnly={!hasSavePermission} fieldTypes={fieldTypes}
useAPIKey={auth.useAPIKey} filter={(field) => field?.admin?.position !== 'sidebar'}
className={`${baseClass}__auth`} permissions={permissions.fields}
readOnly={!hasSavePermission}
/>
</Gutter>
<Gutter className={`${baseClass}__payload-settings`}>
<h3>{t('general:payloadSettings')}</h3>
<div className={`${baseClass}__language`}>
<Label htmlFor="language-select" label={t('general:language')} />
<ReactSelect
inputId="language-select"
onChange={({ value }) => i18n.changeLanguage(value)}
options={languageOptions}
value={languageOptions.find((language) => language.value === i18n.language)}
/> />
</div>
<ToggleTheme />
</Gutter>
</div>
</div>
<div className={`${baseClass}__sidebar-wrap`}>
<div className={`${baseClass}__sidebar`}>
<div className={`${baseClass}__sidebar-sticky-wrap`}>
<div className={`${baseClass}__sidebar-fields`}>
<RenderFields <RenderFields
fieldSchema={fields} fieldSchema={fields}
fieldTypes={fieldTypes} fieldTypes={fieldTypes}
filter={(field) => field?.admin?.position !== 'sidebar'} filter={(field) => field?.admin?.position === 'sidebar'}
permissions={permissions.fields} permissions={permissions.fields}
readOnly={!hasSavePermission} readOnly={!hasSavePermission}
/> />
</Gutter>
<Gutter className={`${baseClass}__payload-settings`}>
<h3>{t('general:payloadSettings')}</h3>
<div className={`${baseClass}__language`}>
<Label htmlFor="language-select" label={t('general:language')} />
<ReactSelect
inputId="language-select"
onChange={({ value }) => i18n.changeLanguage(value)}
options={languageOptions}
value={languageOptions.find(
(language) => language.value === i18n.language,
)}
/>
</div>
<ToggleTheme />
</Gutter>
</div>
</div>
<div className={`${baseClass}__sidebar-wrap`}>
<div className={`${baseClass}__sidebar`}>
<div className={`${baseClass}__sidebar-sticky-wrap`}>
<div className={`${baseClass}__sidebar-fields`}>
<RenderFields
fieldSchema={fields}
fieldTypes={fieldTypes}
filter={(field) => field?.admin?.position === 'sidebar'}
permissions={permissions.fields}
readOnly={!hasSavePermission}
/>
</div>
</div> </div>
</div> </div>
</div> </div>
</Form> </div>
</OperationContext.Provider> </Form>
</div> </OperationContext.Provider>
)} </div>
</React.Fragment> )}
) </React.Fragment>
} )
return null
} }
export default DefaultAccount export default DefaultAccount

View File

@@ -1,6 +1,8 @@
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import type { GlobalEditViewProps } from '../types'
import { getTranslation } from '../../../../utilities/getTranslation' import { getTranslation } from '../../../../utilities/getTranslation'
import { DocumentHeader } from '../../elements/DocumentHeader' import { DocumentHeader } from '../../elements/DocumentHeader'
import { FormLoadingOverlayToggle } from '../../elements/Loading' import { FormLoadingOverlayToggle } from '../../elements/Loading'
@@ -9,67 +11,62 @@ import { OperationContext } from '../../utilities/OperationProvider'
import { GlobalRoutes } from './Routes' import { GlobalRoutes } from './Routes'
import { CustomGlobalComponent } from './Routes/CustomComponent' import { CustomGlobalComponent } from './Routes/CustomComponent'
import './index.scss' import './index.scss'
import { EditViewProps } from '../types'
const baseClass = 'global-edit' const baseClass = 'global-edit'
const DefaultGlobalView: React.FC< const DefaultGlobalView: React.FC<
EditViewProps & { GlobalEditViewProps & {
disableRoutes?: boolean disableRoutes?: boolean
} }
> = (props) => { > = (props) => {
if ('global' in props) { const { i18n } = useTranslation('general')
const {
action,
apiURL,
data,
disableRoutes,
global,
initialState,
isLoading,
onSave,
permissions,
} = props
const { i18n } = useTranslation('general') const {
action,
apiURL,
data,
disableRoutes,
global,
initialState,
isLoading,
onSave,
permissions,
} = props
const { label } = global const { label } = global
const hasSavePermission = permissions?.update?.permission const hasSavePermission = permissions?.update?.permission
return ( return (
<main className={baseClass}> <main className={baseClass}>
<OperationContext.Provider value="update"> <OperationContext.Provider value="update">
<Form <Form
action={action} action={action}
className={`${baseClass}__form`} className={`${baseClass}__form`}
disabled={!hasSavePermission} disabled={!hasSavePermission}
initialState={initialState} initialState={initialState}
method="post" method="post"
onSuccess={onSave} onSuccess={onSave}
> >
<FormLoadingOverlayToggle <FormLoadingOverlayToggle
action="update" action="update"
loadingSuffix={getTranslation(label, i18n)} loadingSuffix={getTranslation(label, i18n)}
name={`global-edit--${typeof label === 'string' ? label : label?.en}`} name={`global-edit--${typeof label === 'string' ? label : label?.en}`}
/> />
{!isLoading && ( {!isLoading && (
<React.Fragment> <React.Fragment>
<DocumentHeader apiURL={apiURL} data={data} global={global} /> <DocumentHeader apiURL={apiURL} data={data} global={global} />
{disableRoutes ? ( {disableRoutes ? (
<CustomGlobalComponent view="Default" {...props} /> <CustomGlobalComponent view="Default" {...props} />
) : ( ) : (
<GlobalRoutes {...props} /> <GlobalRoutes {...props} />
)} )}
</React.Fragment> </React.Fragment>
)} )}
</Form> </Form>
</OperationContext.Provider> </OperationContext.Provider>
</main> </main>
) )
}
return null
} }
export default DefaultGlobalView export default DefaultGlobalView

View File

@@ -1,6 +1,8 @@
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import type { GlobalEditViewProps } from '../../types'
import { getTranslation } from '../../../../../utilities/getTranslation' import { getTranslation } from '../../../../../utilities/getTranslation'
import { DocumentControls } from '../../../elements/DocumentControls' import { DocumentControls } from '../../../elements/DocumentControls'
import { Gutter } from '../../../elements/Gutter' import { Gutter } from '../../../elements/Gutter'
@@ -10,100 +12,96 @@ import { filterFields } from '../../../forms/RenderFields/filterFields'
import { fieldTypes } from '../../../forms/field-types' import { fieldTypes } from '../../../forms/field-types'
import LeaveWithoutSaving from '../../../modals/LeaveWithoutSaving' import LeaveWithoutSaving from '../../../modals/LeaveWithoutSaving'
import Meta from '../../../utilities/Meta' import Meta from '../../../utilities/Meta'
import { SetStepNav } from '../../collections/Edit/SetStepNav'
import './index.scss' import './index.scss'
import { EditViewProps } from '../../types'
const baseClass = 'global-default-edit' const baseClass = 'global-default-edit'
export const DefaultGlobalEdit: React.FC<EditViewProps> = (props) => { export const DefaultGlobalEdit: React.FC<GlobalEditViewProps> = (props) => {
if ('global' in props) { const { i18n } = useTranslation('general')
const { apiURL, data, global, permissions } = props
const { i18n } = useTranslation('general') const { apiURL, data, global, permissions } = props
const { admin: { description } = {}, fields, label } = global const { admin: { description } = {}, fields, label } = global
const hasSavePermission = permissions?.update?.permission const hasSavePermission = permissions?.update?.permission
const sidebarFields = filterFields({ const sidebarFields = filterFields({
fieldSchema: fields, fieldSchema: fields,
fieldTypes, fieldTypes,
filter: (field) => field?.admin?.position === 'sidebar', filter: (field) => field?.admin?.position === 'sidebar',
permissions: permissions.fields, permissions: permissions.fields,
readOnly: !hasSavePermission, readOnly: !hasSavePermission,
}) })
const hasSidebar = sidebarFields && sidebarFields.length > 0 const hasSidebar = sidebarFields && sidebarFields.length > 0
return ( return (
<React.Fragment> <React.Fragment>
{/* <SetStepNav collection={collection} id={id} isEditing={isEditing} /> */} <SetStepNav global={global} />
<DocumentControls <DocumentControls
apiURL={apiURL} apiURL={apiURL}
data={data} data={data}
global={global} global={global}
hasSavePermission={hasSavePermission} hasSavePermission={hasSavePermission}
isEditing isEditing
permissions={permissions} permissions={permissions}
/> />
<div <div
className={[ className={[
baseClass, baseClass,
hasSidebar ? `${baseClass}--has-sidebar` : `${baseClass}--no-sidebar`, hasSidebar ? `${baseClass}--has-sidebar` : `${baseClass}--no-sidebar`,
] ]
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
> >
<div className={`${baseClass}__main`}> <div className={`${baseClass}__main`}>
<Meta <Meta
description={getTranslation(label, i18n)} description={getTranslation(label, i18n)}
keywords={`${getTranslation(label, i18n)}, Payload, CMS`} keywords={`${getTranslation(label, i18n)}, Payload, CMS`}
title={getTranslation(label, i18n)} title={getTranslation(label, i18n)}
/>
{!(global.versions?.drafts && global.versions?.drafts?.autosave) && (
<LeaveWithoutSaving />
)}
<Gutter className={`${baseClass}__edit`}>
<header className={`${baseClass}__header`}>
{description && (
<div className={`${baseClass}__sub-header`}>
<ViewDescription description={description} />
</div>
)}
</header>
<RenderFields
fieldSchema={fields}
fieldTypes={fieldTypes}
filter={(field) =>
!field.admin.position ||
(field.admin.position && field.admin.position !== 'sidebar')
}
permissions={permissions.fields}
readOnly={!hasSavePermission}
/> />
{!(global.versions?.drafts && global.versions?.drafts?.autosave) && ( </Gutter>
<LeaveWithoutSaving /> </div>
)} {hasSidebar && (
<Gutter className={`${baseClass}__edit`}> <div className={`${baseClass}__sidebar-wrap`}>
<header className={`${baseClass}__header`}> <div className={`${baseClass}__sidebar`}>
{description && ( <div className={`${baseClass}__sidebar-sticky-wrap`}>
<div className={`${baseClass}__sub-header`}> <div className={`${baseClass}__sidebar-fields`}>
<ViewDescription description={description} /> <RenderFields
</div> fieldSchema={fields}
)} fieldTypes={fieldTypes}
</header> filter={(field) => field.admin.position === 'sidebar'}
<RenderFields permissions={permissions.fields}
fieldSchema={fields} readOnly={!hasSavePermission}
fieldTypes={fieldTypes} />
filter={(field) =>
!field.admin.position ||
(field.admin.position && field.admin.position !== 'sidebar')
}
permissions={permissions.fields}
readOnly={!hasSavePermission}
/>
</Gutter>
</div>
{hasSidebar && (
<div className={`${baseClass}__sidebar-wrap`}>
<div className={`${baseClass}__sidebar`}>
<div className={`${baseClass}__sidebar-sticky-wrap`}>
<div className={`${baseClass}__sidebar-fields`}>
<RenderFields
fieldSchema={fields}
fieldTypes={fieldTypes}
filter={(field) => field.admin.position === 'sidebar'}
permissions={permissions.fields}
readOnly={!hasSavePermission}
/>
</div>
</div> </div>
</div> </div>
</div> </div>
)} </div>
</div> )}
</React.Fragment> </div>
) </React.Fragment>
} )
return null
} }

View File

@@ -1,9 +1,10 @@
import React from 'react' import React from 'react'
import type { GlobalEditViewProps } from '../../types'
import VersionView from '../../Version/Version' import VersionView from '../../Version/Version'
import VersionsView from '../../Versions' import VersionsView from '../../Versions'
import { DefaultGlobalEdit } from '../Default/index' import { DefaultGlobalEdit } from '../Default/index'
import { EditViewProps } from '../../types'
export type globalViewType = export type globalViewType =
| 'API' | 'API'
@@ -27,37 +28,33 @@ export const defaultGlobalViews: {
} }
export const CustomGlobalComponent = ( export const CustomGlobalComponent = (
args: EditViewProps & { args: GlobalEditViewProps & {
view: globalViewType view: globalViewType
}, },
) => { ) => {
if ('global' in args) { const { global, view } = args
const { global, view } = args
const { admin: { components: { views: { Edit } = {} } = {} } = {} } = global const { admin: { components: { views: { Edit } = {} } = {} } = {} } = global
// Overriding components may come from multiple places in the config // Overriding components may come from multiple places in the config
// Need to cascade through the hierarchy to find the correct component to render // Need to cascade through the hierarchy to find the correct component to render
// For example, the Edit view: // For example, the Edit view:
// 1. Edit?.Default // 1. Edit?.Default
// 2. Edit?.Default?.Component // 2. Edit?.Default?.Component
// TODO: Remove the `@ts-ignore` when a Typescript wizard arrives // TODO: Remove the `@ts-ignore` when a Typescript wizard arrives
// For some reason `Component` does not exist on type `Edit[view]` no matter how narrow the type is // For some reason `Component` does not exist on type `Edit[view]` no matter how narrow the type is
const Component = const Component =
typeof Edit === 'object' && typeof Edit[view] === 'function' typeof Edit === 'object' && typeof Edit[view] === 'function'
? Edit[view] ? Edit[view]
: typeof Edit === 'object' && : typeof Edit === 'object' &&
typeof Edit?.[view] === 'object' && typeof Edit?.[view] === 'object' &&
// @ts-ignore // @ts-ignore
typeof Edit[view].Component === 'function' typeof Edit[view].Component === 'function'
? // @ts-ignore ? // @ts-ignore
Edit[view].Component Edit[view].Component
: defaultGlobalViews[view] : defaultGlobalViews[view]
if (Component) { if (Component) {
return <Component {...args} /> return <Component {...args} />
}
} }
return null
} }

View File

@@ -2,74 +2,71 @@ import { lazy } from 'react'
import React from 'react' import React from 'react'
import { Route, Switch, useRouteMatch } from 'react-router-dom' import { Route, Switch, useRouteMatch } from 'react-router-dom'
import type { GlobalEditViewProps } from '../../types'
import { useAuth } from '../../../utilities/Auth' import { useAuth } from '../../../utilities/Auth'
import { useConfig } from '../../../utilities/Config' import { useConfig } from '../../../utilities/Config'
import NotFound from '../../NotFound' import NotFound from '../../NotFound'
import { CustomGlobalComponent } from './CustomComponent' import { CustomGlobalComponent } from './CustomComponent'
import { globalCustomRoutes } from './custom' import { globalCustomRoutes } from './custom'
import { EditViewProps } from '../../types'
// @ts-expect-error Just TypeScript being broken // TODO: Open TypeScript issue // @ts-expect-error Just TypeScript being broken // TODO: Open TypeScript issue
const Unauthorized = lazy(() => import('../../Unauthorized')) const Unauthorized = lazy(() => import('../../Unauthorized'))
export const GlobalRoutes: React.FC<EditViewProps> = (props) => { export const GlobalRoutes: React.FC<GlobalEditViewProps> = (props) => {
if ('global' in props) { const { global, permissions } = props
const { global, permissions } = props
const match = useRouteMatch() const match = useRouteMatch()
const { const {
routes: { admin: adminRoute }, routes: { admin: adminRoute },
} = useConfig() } = useConfig()
const { user } = useAuth() const { user } = useAuth()
return ( return (
<Switch> <Switch>
<Route <Route
exact exact
key={`${global.slug}-versions`} key={`${global.slug}-versions`}
path={`${adminRoute}/globals/${global.slug}/versions`} path={`${adminRoute}/globals/${global.slug}/versions`}
> >
{permissions?.readVersions?.permission ? ( {permissions?.readVersions?.permission ? (
<CustomGlobalComponent view="Versions" {...props} /> <CustomGlobalComponent view="Versions" {...props} />
) : ( ) : (
<Unauthorized /> <Unauthorized />
)} )}
</Route> </Route>
<Route <Route
exact exact
key={`${global.slug}-view-version`} key={`${global.slug}-view-version`}
path={`${adminRoute}/globals/${global.slug}/versions/:versionID`} path={`${adminRoute}/globals/${global.slug}/versions/:versionID`}
> >
{permissions?.readVersions?.permission ? ( {permissions?.readVersions?.permission ? (
<CustomGlobalComponent view="Version" {...props} /> <CustomGlobalComponent view="Version" {...props} />
) : ( ) : (
<Unauthorized /> <Unauthorized />
)} )}
</Route> </Route>
<Route <Route
exact exact
key={`${global.slug}-live-preview`} key={`${global.slug}-live-preview`}
path={`${adminRoute}/globals/${global.slug}/preview`} path={`${adminRoute}/globals/${global.slug}/preview`}
> >
<CustomGlobalComponent view="LivePreview" {...props} /> <CustomGlobalComponent view="LivePreview" {...props} />
</Route> </Route>
{globalCustomRoutes({ {globalCustomRoutes({
global, global,
match, match,
permissions, permissions,
user, user,
})} })}
<Route exact key={`${global.slug}-view`} path={`${adminRoute}/globals/${global.slug}`}> <Route exact key={`${global.slug}-view`} path={`${adminRoute}/globals/${global.slug}`}>
<CustomGlobalComponent view="Default" {...props} /> <CustomGlobalComponent view="Default" {...props} />
</Route> </Route>
<Route path={`${match.url}*`}> <Route path={`${match.url}*`}>
<NotFound marginTop="large" /> <NotFound marginTop="large" />
</Route> </Route>
</Switch> </Switch>
) )
}
return null
} }

View File

@@ -3,27 +3,25 @@ import { useTranslation } from 'react-i18next'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import type { Fields } from '../../forms/Form/types' import type { Fields } from '../../forms/Form/types'
import type { GlobalEditViewProps } from '../types'
import type { IndexProps } from './types' import type { IndexProps } from './types'
import usePayloadAPI from '../../../hooks/usePayloadAPI' import usePayloadAPI from '../../../hooks/usePayloadAPI'
import { useStepNav } from '../../elements/StepNav'
import buildStateFromSchema from '../../forms/Form/buildStateFromSchema' import buildStateFromSchema from '../../forms/Form/buildStateFromSchema'
import { useAuth } from '../../utilities/Auth' import { useAuth } from '../../utilities/Auth'
import { useConfig } from '../../utilities/Config' import { useConfig } from '../../utilities/Config'
import { useDocumentInfo } from '../../utilities/DocumentInfo' import { useDocumentInfo } from '../../utilities/DocumentInfo'
import { EditDepthContext } from '../../utilities/EditDepth'
import { useLocale } from '../../utilities/Locale' import { useLocale } from '../../utilities/Locale'
import { usePreferences } from '../../utilities/Preferences' import { usePreferences } from '../../utilities/Preferences'
import RenderCustomComponent from '../../utilities/RenderCustomComponent' import RenderCustomComponent from '../../utilities/RenderCustomComponent'
import DefaultGlobalView from './Default' import DefaultGlobalView from './Default'
import { EditDepthContext } from '../../utilities/EditDepth'
import { EditViewProps } from '../types'
const GlobalView: React.FC<IndexProps> = (props) => { const GlobalView: React.FC<IndexProps> = (props) => {
const { global } = props const { global } = props
const { state: locationState } = useLocation<{ data?: Record<string, unknown> }>() const { state: locationState } = useLocation<{ data?: Record<string, unknown> }>()
const { code: locale } = useLocale() const { code: locale } = useLocale()
const { setStepNav } = useStepNav()
const { permissions, user } = useAuth() const { permissions, user } = useAuth()
const [initialState, setInitialState] = useState<Fields>() const [initialState, setInitialState] = useState<Fields>()
const [updatedAt, setUpdatedAt] = useState<string>() const [updatedAt, setUpdatedAt] = useState<string>()
@@ -37,12 +35,7 @@ const GlobalView: React.FC<IndexProps> = (props) => {
serverURL, serverURL,
} = useConfig() } = useConfig()
const { const { admin: { components: { views: { Edit: Edit } = {} } = {} } = {}, fields, slug } = global
admin: { components: { views: { Edit: Edit } = {} } = {} } = {},
fields,
label,
slug,
} = global
const onSave = useCallback( const onSave = useCallback(
async (json) => { async (json) => {
@@ -71,16 +64,6 @@ const GlobalView: React.FC<IndexProps> = (props) => {
const dataToRender = locationState?.data || data const dataToRender = locationState?.data || data
useEffect(() => {
const nav = [
{
label,
},
]
setStepNav(nav)
}, [setStepNav, label])
useEffect(() => { useEffect(() => {
const awaitInitialState = async () => { const awaitInitialState = async () => {
const preferences = await getDocPreferences() const preferences = await getDocPreferences()
@@ -102,7 +85,7 @@ const GlobalView: React.FC<IndexProps> = (props) => {
const isLoading = !initialState || !docPermissions || isLoadingData const isLoading = !initialState || !docPermissions || isLoadingData
const componentProps: EditViewProps = { const componentProps: GlobalEditViewProps = {
action: `${serverURL}${api}/globals/${slug}?locale=${locale}&fallback-locale=null`, action: `${serverURL}${api}/globals/${slug}?locale=${locale}&fallback-locale=null`,
apiURL: `${serverURL}${api}/globals/${slug}?locale=${locale}${ apiURL: `${serverURL}${api}/globals/${slug}?locale=${locale}${
global.versions?.drafts ? '&draft=true' : '' global.versions?.drafts ? '&draft=true' : ''

View File

@@ -12,6 +12,7 @@ import { filterFields } from '../../forms/RenderFields/filterFields'
import { fieldTypes } from '../../forms/field-types' import { fieldTypes } from '../../forms/field-types'
import LeaveWithoutSaving from '../../modals/LeaveWithoutSaving' import LeaveWithoutSaving from '../../modals/LeaveWithoutSaving'
import Meta from '../../utilities/Meta' import Meta from '../../utilities/Meta'
import { SetStepNav } from '../collections/Edit/SetStepNav'
import { LivePreview } from './Preview' import { LivePreview } from './Preview'
import './index.scss' import './index.scss'
import { usePopupWindow } from './usePopupWindow' import { usePopupWindow } from './usePopupWindow'
@@ -71,6 +72,7 @@ export const LivePreviewView: React.FC<EditViewProps> = (props) => {
return ( return (
<Fragment> <Fragment>
<SetStepNav collection={collection} global={global} id={id} isEditing={isEditing} />
<DocumentControls <DocumentControls
apiURL={apiURL} apiURL={apiURL}
collection={collection} collection={collection}

View File

@@ -85,7 +85,7 @@ const VersionView: React.FC<Props> = ({ collection, global }) => {
? originalDocFetchURL ? originalDocFetchURL
: `${compareBaseURL}/${compareValue.value}` : `${compareBaseURL}/${compareValue.value}`
const [{ data: doc, isError, isLoading: isLoadingData }] = usePayloadAPI(versionFetchURL, { const [{ data: doc, isError }] = usePayloadAPI(versionFetchURL, {
initialParams: { depth: 1, locale: '*' }, initialParams: { depth: 1, locale: '*' },
}) })
const [{ data: publishedDoc }] = usePayloadAPI(originalDocFetchURL, { const [{ data: publishedDoc }] = usePayloadAPI(originalDocFetchURL, {

View File

@@ -1,6 +1,8 @@
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import type { CollectionEditViewProps } from '../../types'
import { getTranslation } from '../../../../../utilities/getTranslation' import { getTranslation } from '../../../../../utilities/getTranslation'
import { DocumentHeader } from '../../../elements/DocumentHeader' import { DocumentHeader } from '../../../elements/DocumentHeader'
import { FormLoadingOverlayToggle } from '../../../elements/Loading' import { FormLoadingOverlayToggle } from '../../../elements/Loading'
@@ -9,104 +11,99 @@ import { useAuth } from '../../../utilities/Auth'
import { OperationContext } from '../../../utilities/OperationProvider' import { OperationContext } from '../../../utilities/OperationProvider'
import { CollectionRoutes } from './Routes' import { CollectionRoutes } from './Routes'
import { CustomCollectionComponent } from './Routes/CustomComponent' import { CustomCollectionComponent } from './Routes/CustomComponent'
import { EditViewProps } from '../../types'
import './index.scss' import './index.scss'
const baseClass = 'collection-edit' const baseClass = 'collection-edit'
const DefaultEditView: React.FC< const DefaultEditView: React.FC<
EditViewProps & { CollectionEditViewProps & {
disableRoutes?: boolean
customHeader?: React.ReactNode customHeader?: React.ReactNode
disableRoutes?: boolean
} }
> = (props) => { > = (props) => {
const { i18n } = useTranslation('general') const { i18n } = useTranslation('general')
const { refreshCookieAsync, user } = useAuth() const { refreshCookieAsync, user } = useAuth()
if ('collection' in props) { const {
const { id,
id, action,
action, apiURL,
apiURL, collection,
collection, customHeader,
customHeader, data,
data, disableRoutes,
disableRoutes, hasSavePermission,
hasSavePermission, internalState,
internalState, isEditing,
isEditing, isLoading,
isLoading, onSave: onSaveFromProps,
onSave: onSaveFromProps, } = props
} = props
const { auth } = collection const { auth } = collection
const classes = [baseClass, isEditing && `${baseClass}--is-editing`].filter(Boolean).join(' ') const classes = [baseClass, isEditing && `${baseClass}--is-editing`].filter(Boolean).join(' ')
const onSave = useCallback( const onSave = useCallback(
async (json) => { async (json) => {
if (auth && id === user.id) { if (auth && id === user.id) {
await refreshCookieAsync() await refreshCookieAsync()
} }
if (typeof onSaveFromProps === 'function') { if (typeof onSaveFromProps === 'function') {
onSaveFromProps({ onSaveFromProps({
...json, ...json,
operation: id ? 'update' : 'create', operation: id ? 'update' : 'create',
}) })
} }
}, },
[id, onSaveFromProps, auth, user, refreshCookieAsync], [id, onSaveFromProps, auth, user, refreshCookieAsync],
) )
const operation = isEditing ? 'update' : 'create' const operation = isEditing ? 'update' : 'create'
return ( return (
<main className={classes}> <main className={classes}>
<OperationContext.Provider value={operation}> <OperationContext.Provider value={operation}>
<Form <Form
action={action} action={action}
className={`${baseClass}__form`} className={`${baseClass}__form`}
disabled={!hasSavePermission} disabled={!hasSavePermission}
initialState={internalState} initialState={internalState}
method={id ? 'patch' : 'post'} method={id ? 'patch' : 'post'}
onSuccess={onSave} onSuccess={onSave}
> >
<FormLoadingOverlayToggle <FormLoadingOverlayToggle
action={isLoading ? 'loading' : operation} action={isLoading ? 'loading' : operation}
formIsLoading={isLoading} formIsLoading={isLoading}
loadingSuffix={getTranslation(collection.labels.singular, i18n)} loadingSuffix={getTranslation(collection.labels.singular, i18n)}
name={`collection-edit--${ name={`collection-edit--${
typeof collection?.labels?.singular === 'string' typeof collection?.labels?.singular === 'string'
? collection.labels.singular ? collection.labels.singular
: 'document' : 'document'
}`} }`}
type="withoutNav" type="withoutNav"
/> />
{!isLoading && ( {!isLoading && (
<React.Fragment> <React.Fragment>
<DocumentHeader <DocumentHeader
apiURL={apiURL} apiURL={apiURL}
collection={collection} collection={collection}
customHeader={customHeader} customHeader={customHeader}
data={data} data={data}
id={id} id={id}
isEditing={isEditing} isEditing={isEditing}
/> />
{disableRoutes ? ( {disableRoutes ? (
<CustomCollectionComponent view="Default" {...props} /> <CustomCollectionComponent view="Default" {...props} />
) : ( ) : (
<CollectionRoutes {...props} /> <CollectionRoutes {...props} />
)} )}
</React.Fragment> </React.Fragment>
)} )}
</Form> </Form>
</OperationContext.Provider> </OperationContext.Provider>
</main> </main>
) )
}
return null
} }
export default DefaultEditView export default DefaultEditView

View File

@@ -1,6 +1,8 @@
import React, { Fragment } from 'react' import React, { Fragment } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import type { CollectionEditViewProps } from '../../../types'
import { getTranslation } from '../../../../../../utilities/getTranslation' import { getTranslation } from '../../../../../../utilities/getTranslation'
import { DocumentControls } from '../../../../elements/DocumentControls' import { DocumentControls } from '../../../../elements/DocumentControls'
import { Gutter } from '../../../../elements/Gutter' import { Gutter } from '../../../../elements/Gutter'
@@ -13,115 +15,110 @@ import Auth from '../Auth'
import { SetStepNav } from '../SetStepNav' import { SetStepNav } from '../SetStepNav'
import Upload from '../Upload' import Upload from '../Upload'
import './index.scss' import './index.scss'
import { EditViewProps } from '../../../types'
const baseClass = 'collection-default-edit' const baseClass = 'collection-default-edit'
export const DefaultCollectionEdit: React.FC<EditViewProps> = (props) => { export const DefaultCollectionEdit: React.FC<CollectionEditViewProps> = (props) => {
if ('collection' in props) { const { i18n, t } = useTranslation('general')
const { i18n, t } = useTranslation('general')
const { const {
id, id,
apiURL, apiURL,
collection, collection,
data, data,
disableActions, disableActions,
disableLeaveWithoutSaving, disableLeaveWithoutSaving,
hasSavePermission, hasSavePermission,
internalState, internalState,
isEditing, isEditing,
permissions, permissions,
} = props } = props
const { auth, fields, upload } = collection const { auth, fields, upload } = collection
const operation = isEditing ? 'update' : 'create' const operation = isEditing ? 'update' : 'create'
const sidebarFields = filterFields({ const sidebarFields = filterFields({
fieldSchema: fields, fieldSchema: fields,
fieldTypes, fieldTypes,
filter: (field) => field?.admin?.position === 'sidebar', filter: (field) => field?.admin?.position === 'sidebar',
permissions: permissions.fields, permissions: permissions.fields,
readOnly: !hasSavePermission, readOnly: !hasSavePermission,
}) })
const hasSidebar = sidebarFields && sidebarFields.length > 0 const hasSidebar = sidebarFields && sidebarFields.length > 0
return ( return (
<Fragment> <Fragment>
<SetStepNav collection={collection} id={id} isEditing={isEditing} /> <SetStepNav collection={collection} id={id} isEditing={isEditing} />
<DocumentControls <DocumentControls
apiURL={apiURL} apiURL={apiURL}
collection={collection} collection={collection}
data={data} data={data}
disableActions={disableActions} disableActions={disableActions}
hasSavePermission={hasSavePermission} hasSavePermission={hasSavePermission}
id={id} id={id}
isEditing={isEditing} isEditing={isEditing}
permissions={permissions} permissions={permissions}
/> />
<div <div
className={[ className={[
baseClass, baseClass,
hasSidebar ? `${baseClass}--has-sidebar` : `${baseClass}--no-sidebar`, hasSidebar ? `${baseClass}--has-sidebar` : `${baseClass}--no-sidebar`,
] ]
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
> >
<div className={`${baseClass}__main`}> <div className={`${baseClass}__main`}>
<Meta <Meta
description={`${isEditing ? t('editing') : t('creating')} - ${getTranslation( description={`${isEditing ? t('editing') : t('creating')} - ${getTranslation(
collection.labels.singular, collection.labels.singular,
i18n, i18n,
)}`} )}`}
keywords={`${getTranslation(collection.labels.singular, i18n)}, Payload, CMS`} keywords={`${getTranslation(collection.labels.singular, i18n)}, Payload, CMS`}
title={`${isEditing ? t('editing') : t('creating')} - ${getTranslation( title={`${isEditing ? t('editing') : t('creating')} - ${getTranslation(
collection.labels.singular, collection.labels.singular,
i18n, i18n,
)}`} )}`}
/> />
{!(collection.versions?.drafts && collection.versions?.drafts?.autosave) && {!(collection.versions?.drafts && collection.versions?.drafts?.autosave) &&
!disableLeaveWithoutSaving && <LeaveWithoutSaving />} !disableLeaveWithoutSaving && <LeaveWithoutSaving />}
<Gutter className={`${baseClass}__edit`}> <Gutter className={`${baseClass}__edit`}>
{auth && ( {auth && (
<Auth <Auth
className={`${baseClass}__auth`} className={`${baseClass}__auth`}
collection={collection} collection={collection}
email={data?.email} email={data?.email}
operation={operation} operation={operation}
readOnly={!hasSavePermission}
requirePassword={!isEditing}
useAPIKey={auth.useAPIKey}
verify={auth.verify}
/>
)}
{upload && (
<Upload collection={collection} data={data} internalState={internalState} />
)}
<RenderFields
fieldSchema={fields}
fieldTypes={fieldTypes}
filter={(field) => !field?.admin?.position || field?.admin?.position !== 'sidebar'}
permissions={permissions.fields}
readOnly={!hasSavePermission} readOnly={!hasSavePermission}
className={`${baseClass}__fields`} requirePassword={!isEditing}
useAPIKey={auth.useAPIKey}
verify={auth.verify}
/> />
</Gutter> )}
</div> {upload && <Upload collection={collection} data={data} internalState={internalState} />}
{hasSidebar && ( <RenderFields
<div className={`${baseClass}__sidebar-wrap`}> className={`${baseClass}__fields`}
<div className={`${baseClass}__sidebar`}> fieldSchema={fields}
<div className={`${baseClass}__sidebar-sticky-wrap`}> fieldTypes={fieldTypes}
<div className={`${baseClass}__sidebar-fields`}> filter={(field) => !field?.admin?.position || field?.admin?.position !== 'sidebar'}
<RenderFields fieldTypes={fieldTypes} fields={sidebarFields} /> permissions={permissions.fields}
</div> readOnly={!hasSavePermission}
/>
</Gutter>
</div>
{hasSidebar && (
<div className={`${baseClass}__sidebar-wrap`}>
<div className={`${baseClass}__sidebar`}>
<div className={`${baseClass}__sidebar-sticky-wrap`}>
<div className={`${baseClass}__sidebar-fields`}>
<RenderFields fieldTypes={fieldTypes} fields={sidebarFields} />
</div> </div>
</div> </div>
</div> </div>
)} </div>
</div> )}
</Fragment> </div>
) </Fragment>
} )
} }

View File

@@ -1,12 +1,12 @@
import React from 'react' import React from 'react'
import type { EditViewProps } from '../../../types' import type { CollectionEditViewProps } from '../../../types'
import { LivePreviewView } from '../../../LivePreview'
import { QueryInspector } from '../../../RestAPI' import { QueryInspector } from '../../../RestAPI'
import VersionView from '../../../Version/Version' import VersionView from '../../../Version/Version'
import VersionsView from '../../../Versions' import VersionsView from '../../../Versions'
import { DefaultCollectionEdit } from '../Default/index' import { DefaultCollectionEdit } from '../Default/index'
import { LivePreviewView } from '../../../LivePreview'
export type collectionViewType = export type collectionViewType =
| 'API' | 'API'
@@ -30,37 +30,33 @@ export const defaultCollectionViews: {
} }
export const CustomCollectionComponent = ( export const CustomCollectionComponent = (
args: EditViewProps & { args: CollectionEditViewProps & {
view: collectionViewType view: collectionViewType
}, },
) => { ) => {
if ('collection' in args) { const { collection, view } = args
const { collection, view } = args
const { admin: { components: { views: { Edit } = {} } = {} } = {} } = collection const { admin: { components: { views: { Edit } = {} } = {} } = {} } = collection
// Overriding components may come from multiple places in the config // Overriding components may come from multiple places in the config
// Need to cascade through the hierarchy to find the correct component to render // Need to cascade through the hierarchy to find the correct component to render
// For example, the Edit view: // For example, the Edit view:
// 1. Edit?.Default // 1. Edit?.Default
// 2. Edit?.Default?.Component // 2. Edit?.Default?.Component
// TODO: Remove the `@ts-ignore` when a Typescript wizard arrives // TODO: Remove the `@ts-ignore` when a Typescript wizard arrives
// For some reason `Component` does not exist on type `Edit[view]` no matter how narrow the type is // For some reason `Component` does not exist on type `Edit[view]` no matter how narrow the type is
const Component = const Component =
typeof Edit === 'object' && typeof Edit[view] === 'function' typeof Edit === 'object' && typeof Edit[view] === 'function'
? Edit[view] ? Edit[view]
: typeof Edit === 'object' && : typeof Edit === 'object' &&
typeof Edit?.[view] === 'object' && typeof Edit?.[view] === 'object' &&
// @ts-ignore // @ts-ignore
typeof Edit[view].Component === 'function' typeof Edit[view].Component === 'function'
? // @ts-ignore ? // @ts-ignore
Edit[view].Component Edit[view].Component
: defaultCollectionViews[view] : defaultCollectionViews[view]
if (Component) { if (Component) {
return <Component {...args} /> return <Component {...args} />
}
} }
return null
} }

View File

@@ -2,7 +2,7 @@ import { lazy } from 'react'
import React from 'react' import React from 'react'
import { Route, Switch, useRouteMatch } from 'react-router-dom' import { Route, Switch, useRouteMatch } from 'react-router-dom'
import type { EditViewProps } from '../../../types' import type { CollectionEditViewProps } from '../../../types'
import { useAuth } from '../../../../utilities/Auth' import { useAuth } from '../../../../utilities/Auth'
import { useConfig } from '../../../../utilities/Config' import { useConfig } from '../../../../utilities/Config'
@@ -13,77 +13,71 @@ import { collectionCustomRoutes } from './custom'
// @ts-expect-error Just TypeScript being broken // TODO: Open TypeScript issue // @ts-expect-error Just TypeScript being broken // TODO: Open TypeScript issue
const Unauthorized = lazy(() => import('../../../Unauthorized')) const Unauthorized = lazy(() => import('../../../Unauthorized'))
export const CollectionRoutes: React.FC<EditViewProps> = (props) => { export const CollectionRoutes: React.FC<CollectionEditViewProps> = (props) => {
if ('collection' in props) { const { collection, permissions } = props
const { collection, permissions } = props
const match = useRouteMatch() const match = useRouteMatch()
const { const {
routes: { admin: adminRoute }, routes: { admin: adminRoute },
} = useConfig() } = useConfig()
const { user } = useAuth() const { user } = useAuth()
return ( return (
<Switch> <Switch>
<Route <Route
exact exact
key={`${collection.slug}-versions`} key={`${collection.slug}-versions`}
path={`${adminRoute}/collections/${collection.slug}/:id/versions`} path={`${adminRoute}/collections/${collection.slug}/:id/versions`}
> >
{permissions?.readVersions?.permission ? ( {permissions?.readVersions?.permission ? (
<CustomCollectionComponent view="Versions" {...props} /> <CustomCollectionComponent view="Versions" {...props} />
) : ( ) : (
<Unauthorized /> <Unauthorized />
)} )}
</Route> </Route>
<Route <Route
exact exact
key={`${collection.slug}-api`} key={`${collection.slug}-api`}
path={`${adminRoute}/collections/${collection.slug}/:id/api`} path={`${adminRoute}/collections/${collection.slug}/:id/api`}
> >
{permissions?.read ? ( {permissions?.read ? <CustomCollectionComponent view="API" {...props} /> : <Unauthorized />}
<CustomCollectionComponent view="API" {...props} /> </Route>
) : ( <Route
<Unauthorized /> exact
)} key={`${collection.slug}-view-version`}
</Route> path={`${adminRoute}/collections/${collection.slug}/:id/versions/:versionID`}
<Route >
exact {permissions?.readVersions?.permission ? (
key={`${collection.slug}-view-version`} <CustomCollectionComponent view="Version" {...props} />
path={`${adminRoute}/collections/${collection.slug}/:id/versions/:versionID`} ) : (
> <Unauthorized />
{permissions?.readVersions?.permission ? ( )}
<CustomCollectionComponent view="Version" {...props} /> </Route>
) : ( <Route
<Unauthorized /> exact
)} key={`${collection.slug}-live-preview`}
</Route> path={`${adminRoute}/collections/${collection.slug}/:id/preview`}
<Route >
exact <CustomCollectionComponent view="LivePreview" {...props} />
key={`${collection.slug}-live-preview`} </Route>
path={`${adminRoute}/collections/${collection.slug}/:id/preview`} {collectionCustomRoutes({
> collection,
<CustomCollectionComponent view="LivePreview" {...props} /> match,
</Route> permissions,
{collectionCustomRoutes({ user,
collection, })}
match, <Route
permissions, exact
user, key={`${collection.slug}-view`}
})} path={`${adminRoute}/collections/${collection.slug}/:id`}
<Route >
exact <CustomCollectionComponent view="Default" {...props} />
key={`${collection.slug}-view`} </Route>
path={`${adminRoute}/collections/${collection.slug}/:id`} <Route path={`${match.url}*`}>
> <NotFound marginTop="large" />
<CustomCollectionComponent view="Default" {...props} /> </Route>
</Route> </Switch>
<Route path={`${match.url}*`}> )
<NotFound marginTop="large" />
</Route>
</Switch>
)
}
} }

View File

@@ -2,6 +2,7 @@ import { useEffect } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import type { SanitizedCollectionConfig } from '../../../../../collections/config/types' import type { SanitizedCollectionConfig } from '../../../../../collections/config/types'
import type { SanitizedGlobalConfig } from '../../../../../exports/types'
import type { StepNavItem } from '../../../elements/StepNav/types' import type { StepNavItem } from '../../../elements/StepNav/types'
import { getTranslation } from '../../../../../utilities/getTranslation' import { getTranslation } from '../../../../../utilities/getTranslation'
@@ -9,45 +10,96 @@ import useTitle from '../../../../hooks/useTitle'
import { useStepNav } from '../../../elements/StepNav' import { useStepNav } from '../../../elements/StepNav'
import { useConfig } from '../../../utilities/Config' import { useConfig } from '../../../utilities/Config'
export const SetStepNav: React.FC<{ export const SetStepNav: React.FC<
collection: SanitizedCollectionConfig | {
id: string collection: SanitizedCollectionConfig
isEditing: boolean id: string
}> = ({ id, collection, isEditing }) => { isEditing: boolean
const { }
admin: { useAsTitle }, | {
labels: { plural: pluralLabel }, global: SanitizedGlobalConfig
slug, }
} = collection > = (props) => {
let collection: SanitizedCollectionConfig | undefined
let global: SanitizedGlobalConfig | undefined
let useAsTitle: string | undefined
let pluralLabel: SanitizedCollectionConfig['labels']['plural']
let slug: string
let isEditing = false
let id: string | undefined
// This only applies to collections
const title = useTitle(collection)
if ('collection' in props) {
const {
id: idFromProps,
collection: collectionFromProps,
isEditing: isEditingFromProps,
} = props
collection = collectionFromProps
useAsTitle = collection.admin.useAsTitle
pluralLabel = collection.labels.plural
slug = collection.slug
isEditing = isEditingFromProps
id = idFromProps
}
if ('global' in props) {
const { global: globalFromProps } = props
global = globalFromProps
slug = globalFromProps?.slug
}
const { setStepNav } = useStepNav() const { setStepNav } = useStepNav()
const { i18n, t } = useTranslation('general') const { i18n, t } = useTranslation('general')
const { const {
routes: { admin }, routes: { admin },
} = useConfig() } = useConfig()
const title = useTitle(collection)
useEffect(() => { useEffect(() => {
const nav: StepNavItem[] = [ const nav: StepNavItem[] = []
{
if (collection) {
nav.push({
label: getTranslation(pluralLabel, i18n), label: getTranslation(pluralLabel, i18n),
url: `${admin}/collections/${slug}`, url: `${admin}/collections/${slug}`,
},
]
if (isEditing) {
nav.push({
label: useAsTitle && useAsTitle !== 'id' ? title || `[${t('untitled')}]` : id,
}) })
} else {
if (isEditing) {
nav.push({
label: useAsTitle && useAsTitle !== 'id' ? title || `[${t('untitled')}]` : id,
})
} else {
nav.push({
label: t('createNew'),
})
}
} else if (global) {
nav.push({ nav.push({
label: t('createNew'), label: getTranslation(global.label, i18n),
url: `${admin}/globals/${slug}`,
}) })
} }
setStepNav(nav) setStepNav(nav)
}, [setStepNav, isEditing, pluralLabel, id, slug, useAsTitle, admin, t, i18n, title]) }, [
setStepNav,
isEditing,
pluralLabel,
id,
slug,
useAsTitle,
admin,
t,
i18n,
title,
global,
collection,
])
return null return null
} }

View File

@@ -4,6 +4,7 @@ import { useHistory, useRouteMatch } from 'react-router-dom'
import type { CollectionPermission } from '../../../../../auth' import type { CollectionPermission } from '../../../../../auth'
import type { Fields } from '../../../forms/Form/types' import type { Fields } from '../../../forms/Form/types'
import type { CollectionEditViewProps } from '../../types'
import type { IndexProps } from './types' import type { IndexProps } from './types'
import usePayloadAPI from '../../../../hooks/usePayloadAPI' import usePayloadAPI from '../../../../hooks/usePayloadAPI'
@@ -17,7 +18,6 @@ import RenderCustomComponent from '../../../utilities/RenderCustomComponent'
import NotFound from '../../NotFound' import NotFound from '../../NotFound'
import DefaultEdit from './Default' import DefaultEdit from './Default'
import formatFields from './formatFields' import formatFields from './formatFields'
import { EditViewProps } from '../../types'
const EditView: React.FC<IndexProps> = (props) => { const EditView: React.FC<IndexProps> = (props) => {
const { collection: incomingCollection, isEditing } = props const { collection: incomingCollection, isEditing } = props
@@ -125,7 +125,7 @@ const EditView: React.FC<IndexProps> = (props) => {
const isLoading = !internalState || !docPermissions || isLoadingData const isLoading = !internalState || !docPermissions || isLoadingData
const componentProps: EditViewProps = { const componentProps: CollectionEditViewProps = {
id, id,
action, action,
apiURL, apiURL,

View File

@@ -5,24 +5,25 @@ import type {
SanitizedGlobalConfig, SanitizedGlobalConfig,
} from '../../../exports/types' } from '../../../exports/types'
export type EditViewProps = ( export type CollectionEditViewProps = BaseEditViewProps & {
| { collection: SanitizedCollectionConfig
collection: SanitizedCollectionConfig disableActions?: boolean
disableActions?: boolean disableLeaveWithoutSaving?: boolean
disableLeaveWithoutSaving?: boolean hasSavePermission: boolean
hasSavePermission: boolean id: string
id: string initialState?: Fields
initialState?: Fields internalState: Fields
internalState: Fields isEditing: boolean
isEditing: boolean permissions: CollectionPermission
permissions: CollectionPermission }
}
| { export type GlobalEditViewProps = BaseEditViewProps & {
global: SanitizedGlobalConfig global: SanitizedGlobalConfig
initialState: Fields initialState: Fields
permissions: GlobalPermission permissions: GlobalPermission
} }
) & {
export type BaseEditViewProps = {
action: string action: string
apiURL: string apiURL: string
canAccessAdmin: boolean canAccessAdmin: boolean
@@ -32,3 +33,5 @@ export type EditViewProps = (
updatedAt: string updatedAt: string
user: User user: User
} }
export type EditViewProps = CollectionEditViewProps | GlobalEditViewProps

View File

@@ -52,11 +52,20 @@ export const formatUseAsTitle = (args: {
return title return title
} }
const useTitle = (collection: SanitizedCollectionConfig): string => { // Keep `collection` optional so that component do need to worry about conditionally rendering hooks
// This is so that components which take both `collection` and `global` props can use this hook
const useTitle = (collection?: SanitizedCollectionConfig): string => {
const { i18n } = useTranslation() const { i18n } = useTranslation()
const field = useFormFields(([formFields]) => formFields[collection?.admin?.useAsTitle])
const field = useFormFields(([formFields]) => {
if (!collection) return
return formFields[collection?.admin?.useAsTitle]
})
const config = useConfig() const config = useConfig()
if (!collection) return ''
return formatUseAsTitle({ collection, config, field, i18n }) return formatUseAsTitle({ collection, config, field, i18n })
} }

View File

@@ -13,7 +13,7 @@ import type { InlineConfig } from 'vite'
import type { DocumentTab } from '../admin/components/elements/DocumentHeader/Tabs/types' import type { DocumentTab } from '../admin/components/elements/DocumentHeader/Tabs/types'
import type { RichTextAdapter } from '../admin/components/forms/field-types/RichText/types' import type { RichTextAdapter } from '../admin/components/forms/field-types/RichText/types'
import type { EditViewProps } from '../admin/components/views/types' import type { CollectionEditViewProps, GlobalEditViewProps } from '../admin/components/views/types'
import type { User } from '../auth/types' import type { User } from '../auth/types'
import type { PayloadBundler } from '../bundlers/types' import type { PayloadBundler } from '../bundlers/types'
import type { import type {
@@ -241,7 +241,7 @@ export type EditViewConfig = {
path: string path: string
} }
export type EditViewComponent = React.ComponentType<EditViewProps> export type EditViewComponent = React.ComponentType<CollectionEditViewProps | GlobalEditViewProps>
export type EditView = EditViewComponent | EditViewConfig export type EditView = EditViewComponent | EditViewConfig

View File

@@ -1,9 +1,10 @@
import React, { Fragment, useEffect } from 'react' import React, { Fragment, useEffect } from 'react'
import { Redirect } from 'react-router-dom' import { Redirect } from 'react-router-dom'
import type { EditViewComponent } from '../../../../../packages/payload/src/config/types'
import { useStepNav } from '../../../../../packages/payload/src/admin/components/elements/StepNav' import { useStepNav } from '../../../../../packages/payload/src/admin/components/elements/StepNav'
import { useConfig } from '../../../../../packages/payload/src/admin/components/utilities/Config' import { useConfig } from '../../../../../packages/payload/src/admin/components/utilities/Config'
import { EditViewComponent } from '../../../../../packages/payload/src/config/types'
const CustomDefaultView: EditViewComponent = ({ const CustomDefaultView: EditViewComponent = ({
canAccessAdmin, canAccessAdmin,

View File

@@ -1,9 +1,10 @@
import React, { Fragment, useEffect } from 'react' import React, { Fragment, useEffect } from 'react'
import { Redirect } from 'react-router-dom' import { Redirect } from 'react-router-dom'
import type { EditViewComponent } from '../../../../../packages/payload/src/config/types'
import { useStepNav } from '../../../../../packages/payload/src/admin/components/elements/StepNav' import { useStepNav } from '../../../../../packages/payload/src/admin/components/elements/StepNav'
import { useConfig } from '../../../../../packages/payload/src/admin/components/utilities/Config' import { useConfig } from '../../../../../packages/payload/src/admin/components/utilities/Config'
import { EditViewComponent } from '../../../../../packages/payload/src/config/types'
const CustomEditView: EditViewComponent = ({ const CustomEditView: EditViewComponent = ({
canAccessAdmin, canAccessAdmin,

View File

@@ -1,9 +1,10 @@
import React, { Fragment, useEffect } from 'react' import React, { Fragment, useEffect } from 'react'
import { Redirect } from 'react-router-dom' import { Redirect } from 'react-router-dom'
import type { EditViewComponent } from '../../../../../packages/payload/src/config/types'
import { useStepNav } from '../../../../../packages/payload/src/admin/components/elements/StepNav' import { useStepNav } from '../../../../../packages/payload/src/admin/components/elements/StepNav'
import { useConfig } from '../../../../../packages/payload/src/admin/components/utilities/Config' import { useConfig } from '../../../../../packages/payload/src/admin/components/utilities/Config'
import { EditViewComponent } from '../../../../../packages/payload/src/config/types'
const CustomVersionsView: EditViewComponent = ({ const CustomVersionsView: EditViewComponent = ({
canAccessAdmin, canAccessAdmin,

View File

@@ -19,7 +19,7 @@ export default buildConfigWithDefaults({
slug: 'users', slug: 'users',
auth: true, auth: true,
admin: { admin: {
useAsTitle: 'email', useAsTitle: 'title',
}, },
fields: [], fields: [],
}, },

View File

@@ -9,21 +9,11 @@
export interface Config { export interface Config {
collections: { collections: {
users: User users: User
'hidden-collection': HiddenCollection pages: Page
posts: Post
'group-one-collection-ones': GroupOneCollectionOne
'group-one-collection-twos': GroupOneCollectionTwo
'group-two-collection-ones': GroupTwoCollectionOne
'group-two-collection-twos': GroupTwoCollectionTwo
'payload-preferences': PayloadPreference 'payload-preferences': PayloadPreference
'payload-migrations': PayloadMigration 'payload-migrations': PayloadMigration
} }
globals: { globals: {}
'hidden-global': HiddenGlobal
global: Global
'group-globals-one': GroupGlobalsOne
'group-globals-two': GroupGlobalsTwo
}
} }
export interface User { export interface User {
id: string id: string
@@ -38,52 +28,19 @@ export interface User {
lockUntil?: string lockUntil?: string
password?: string password?: string
} }
export interface HiddenCollection { export interface Page {
id: string id: string
title?: string title: string
updatedAt: string description: string
createdAt: string slug: string
}
export interface Post {
id: string
title?: string
description?: string
number?: number
richText?: {
[k: string]: unknown
}[]
updatedAt: string
createdAt: string
}
export interface GroupOneCollectionOne {
id: string
title?: string
updatedAt: string
createdAt: string
}
export interface GroupOneCollectionTwo {
id: string
title?: string
updatedAt: string
createdAt: string
}
export interface GroupTwoCollectionOne {
id: string
title?: string
updatedAt: string
createdAt: string
}
export interface GroupTwoCollectionTwo {
id: string
title?: string
updatedAt: string updatedAt: string
createdAt: string createdAt: string
} }
export interface PayloadPreference { export interface PayloadPreference {
id: string id: string
user: { user: {
value: string | User
relationTo: 'users' relationTo: 'users'
value: string | User
} }
key?: string key?: string
value?: value?:
@@ -114,27 +71,14 @@ export interface PayloadMigration {
updatedAt: string updatedAt: string
createdAt: string createdAt: string
} }
export interface HiddenGlobal {
id: string declare module 'payload' {
title?: string export interface GeneratedTypes {
updatedAt?: string collections: {
createdAt?: string users: User
} pages: Page
export interface Global { 'payload-preferences': PayloadPreference
id: string 'payload-migrations': PayloadMigration
title?: string }
updatedAt?: string }
createdAt?: string
}
export interface GroupGlobalsOne {
id: string
title?: string
updatedAt?: string
createdAt?: string
}
export interface GroupGlobalsTwo {
id: string
title?: string
updatedAt?: string
createdAt?: string
} }