diff --git a/packages/payload/src/admin/components/views/Account/Default.tsx b/packages/payload/src/admin/components/views/Account/Default.tsx index 4eaa5ea86..620a607ea 100644 --- a/packages/payload/src/admin/components/views/Account/Default.tsx +++ b/packages/payload/src/admin/components/views/Account/Default.tsx @@ -2,6 +2,7 @@ import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' import type { Translation } from '../../../../translations/type' +import type { CollectionEditViewProps } from '../types' import { DocumentControls } from '../../elements/DocumentControls' import { DocumentHeader } from '../../elements/DocumentHeader' @@ -19,134 +20,127 @@ import { OperationContext } from '../../utilities/OperationProvider' import Auth from '../collections/Edit/Auth' import { ToggleTheme } from './ToggleTheme' import './index.scss' -import { EditViewProps } from '../types' const baseClass = 'account' -const DefaultAccount: React.FC = (props) => { - if ('collection' in props) { - const { - action, - apiURL, - collection, - data, - hasSavePermission, - initialState, - isLoading, - onSave: onSaveFromProps, - permissions, - } = props +const DefaultAccount: React.FC = (props) => { + const { + action, + apiURL, + collection, + data, + hasSavePermission, + initialState, + isLoading, + onSave: onSaveFromProps, + permissions, + } = props - const { auth, fields } = collection + const { auth, fields } = collection - const { refreshCookieAsync } = useAuth() - const { i18n, t } = useTranslation('authentication') + const { refreshCookieAsync } = useAuth() + const { i18n, t } = useTranslation('authentication') - const languageOptions = Object.entries(i18n.options.resources).map(([language, resource]) => ({ - label: (resource as Translation).general.thisLanguage, - value: language, - })) + const languageOptions = Object.entries(i18n.options.resources).map(([language, resource]) => ({ + label: (resource as Translation).general.thisLanguage, + value: language, + })) - const onSave = useCallback(async () => { - await refreshCookieAsync() - if (typeof onSaveFromProps === 'function') { - onSaveFromProps({}) - } - }, [onSaveFromProps, refreshCookieAsync]) + const onSave = useCallback(async () => { + await refreshCookieAsync() + if (typeof onSaveFromProps === 'function') { + onSaveFromProps({}) + } + }, [onSaveFromProps, refreshCookieAsync]) - const classes = [baseClass].filter(Boolean).join(' ') + const classes = [baseClass].filter(Boolean).join(' ') - return ( - - - {!isLoading && ( -
- -
- - + + {!isLoading && ( +
+ + + + +
+ -
- - {!(collection.versions?.drafts && collection.versions?.drafts?.autosave) && ( - - )} -
- - + )} +
+ + + field?.admin?.position !== 'sidebar'} + permissions={permissions.fields} + readOnly={!hasSavePermission} + /> + + +

{t('general:payloadSettings')}

+
+
+ +
+
+
+
+
+
+
field?.admin?.position !== 'sidebar'} + filter={(field) => field?.admin?.position === 'sidebar'} permissions={permissions.fields} readOnly={!hasSavePermission} /> - - -

{t('general:payloadSettings')}

-
-
- -
-
-
-
-
-
-
- field?.admin?.position === 'sidebar'} - permissions={permissions.fields} - readOnly={!hasSavePermission} - /> -
- - -
- )} - - ) - } - - return null +
+ + +
+ )} + + ) } export default DefaultAccount diff --git a/packages/payload/src/admin/components/views/Global/Default.tsx b/packages/payload/src/admin/components/views/Global/Default.tsx index f10f1aa18..d50ba30cb 100644 --- a/packages/payload/src/admin/components/views/Global/Default.tsx +++ b/packages/payload/src/admin/components/views/Global/Default.tsx @@ -1,6 +1,8 @@ import React from 'react' import { useTranslation } from 'react-i18next' +import type { GlobalEditViewProps } from '../types' + import { getTranslation } from '../../../../utilities/getTranslation' import { DocumentHeader } from '../../elements/DocumentHeader' import { FormLoadingOverlayToggle } from '../../elements/Loading' @@ -9,67 +11,62 @@ import { OperationContext } from '../../utilities/OperationProvider' import { GlobalRoutes } from './Routes' import { CustomGlobalComponent } from './Routes/CustomComponent' import './index.scss' -import { EditViewProps } from '../types' const baseClass = 'global-edit' const DefaultGlobalView: React.FC< - EditViewProps & { + GlobalEditViewProps & { disableRoutes?: boolean } > = (props) => { - if ('global' in props) { - const { - action, - apiURL, - data, - disableRoutes, - global, - initialState, - isLoading, - onSave, - permissions, - } = props + const { i18n } = useTranslation('general') - 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 ( -
- -
- - {!isLoading && ( - - - {disableRoutes ? ( - - ) : ( - - )} - - )} - -
-
- ) - } - - return null + return ( +
+ +
+ + {!isLoading && ( + + + {disableRoutes ? ( + + ) : ( + + )} + + )} + +
+
+ ) } export default DefaultGlobalView diff --git a/packages/payload/src/admin/components/views/Global/Default/index.tsx b/packages/payload/src/admin/components/views/Global/Default/index.tsx index 8cff9bbc0..01722107c 100644 --- a/packages/payload/src/admin/components/views/Global/Default/index.tsx +++ b/packages/payload/src/admin/components/views/Global/Default/index.tsx @@ -1,6 +1,8 @@ import React from 'react' import { useTranslation } from 'react-i18next' +import type { GlobalEditViewProps } from '../../types' + import { getTranslation } from '../../../../../utilities/getTranslation' import { DocumentControls } from '../../../elements/DocumentControls' import { Gutter } from '../../../elements/Gutter' @@ -10,100 +12,96 @@ import { filterFields } from '../../../forms/RenderFields/filterFields' import { fieldTypes } from '../../../forms/field-types' import LeaveWithoutSaving from '../../../modals/LeaveWithoutSaving' import Meta from '../../../utilities/Meta' +import { SetStepNav } from '../../collections/Edit/SetStepNav' import './index.scss' -import { EditViewProps } from '../../types' const baseClass = 'global-default-edit' -export const DefaultGlobalEdit: React.FC = (props) => { - if ('global' in props) { - const { apiURL, data, global, permissions } = props +export const DefaultGlobalEdit: React.FC = (props) => { + const { i18n } = useTranslation('general') - 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({ - fieldSchema: fields, - fieldTypes, - filter: (field) => field?.admin?.position === 'sidebar', - permissions: permissions.fields, - readOnly: !hasSavePermission, - }) + const sidebarFields = filterFields({ + fieldSchema: fields, + fieldTypes, + filter: (field) => field?.admin?.position === 'sidebar', + permissions: permissions.fields, + readOnly: !hasSavePermission, + }) - const hasSidebar = sidebarFields && sidebarFields.length > 0 + const hasSidebar = sidebarFields && sidebarFields.length > 0 - return ( - - {/* */} - -
-
- + + +
+
+ + {!(global.versions?.drafts && global.versions?.drafts?.autosave) && ( + + )} + +
+ {description && ( +
+ +
+ )} +
+ + !field.admin.position || + (field.admin.position && field.admin.position !== 'sidebar') + } + permissions={permissions.fields} + readOnly={!hasSavePermission} /> - {!(global.versions?.drafts && global.versions?.drafts?.autosave) && ( - - )} - -
- {description && ( -
- -
- )} -
- - !field.admin.position || - (field.admin.position && field.admin.position !== 'sidebar') - } - permissions={permissions.fields} - readOnly={!hasSavePermission} - /> -
-
- {hasSidebar && ( -
-
-
-
- field.admin.position === 'sidebar'} - permissions={permissions.fields} - readOnly={!hasSavePermission} - /> -
+ +
+ {hasSidebar && ( +
+
+
+
+ field.admin.position === 'sidebar'} + permissions={permissions.fields} + readOnly={!hasSavePermission} + />
- )} -
- - ) - } - - return null +
+ )} +
+ + ) } diff --git a/packages/payload/src/admin/components/views/Global/Routes/CustomComponent.tsx b/packages/payload/src/admin/components/views/Global/Routes/CustomComponent.tsx index 9c9959684..25f4418da 100644 --- a/packages/payload/src/admin/components/views/Global/Routes/CustomComponent.tsx +++ b/packages/payload/src/admin/components/views/Global/Routes/CustomComponent.tsx @@ -1,9 +1,10 @@ import React from 'react' +import type { GlobalEditViewProps } from '../../types' + import VersionView from '../../Version/Version' import VersionsView from '../../Versions' import { DefaultGlobalEdit } from '../Default/index' -import { EditViewProps } from '../../types' export type globalViewType = | 'API' @@ -27,37 +28,33 @@ export const defaultGlobalViews: { } export const CustomGlobalComponent = ( - args: EditViewProps & { + args: GlobalEditViewProps & { 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 - // Need to cascade through the hierarchy to find the correct component to render - // For example, the Edit view: - // 1. Edit?.Default - // 2. Edit?.Default?.Component - // 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 - const Component = - typeof Edit === 'object' && typeof Edit[view] === 'function' - ? Edit[view] - : typeof Edit === 'object' && - typeof Edit?.[view] === 'object' && - // @ts-ignore - typeof Edit[view].Component === 'function' - ? // @ts-ignore - Edit[view].Component - : defaultGlobalViews[view] + // Overriding components may come from multiple places in the config + // Need to cascade through the hierarchy to find the correct component to render + // For example, the Edit view: + // 1. Edit?.Default + // 2. Edit?.Default?.Component + // 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 + const Component = + typeof Edit === 'object' && typeof Edit[view] === 'function' + ? Edit[view] + : typeof Edit === 'object' && + typeof Edit?.[view] === 'object' && + // @ts-ignore + typeof Edit[view].Component === 'function' + ? // @ts-ignore + Edit[view].Component + : defaultGlobalViews[view] - if (Component) { - return - } + if (Component) { + return } - - return null } diff --git a/packages/payload/src/admin/components/views/Global/Routes/index.tsx b/packages/payload/src/admin/components/views/Global/Routes/index.tsx index e979a91af..2a1c22749 100644 --- a/packages/payload/src/admin/components/views/Global/Routes/index.tsx +++ b/packages/payload/src/admin/components/views/Global/Routes/index.tsx @@ -2,74 +2,71 @@ import { lazy } from 'react' import React from 'react' import { Route, Switch, useRouteMatch } from 'react-router-dom' +import type { GlobalEditViewProps } from '../../types' + import { useAuth } from '../../../utilities/Auth' import { useConfig } from '../../../utilities/Config' import NotFound from '../../NotFound' import { CustomGlobalComponent } from './CustomComponent' import { globalCustomRoutes } from './custom' -import { EditViewProps } from '../../types' // @ts-expect-error Just TypeScript being broken // TODO: Open TypeScript issue const Unauthorized = lazy(() => import('../../Unauthorized')) -export const GlobalRoutes: React.FC = (props) => { - if ('global' in props) { - const { global, permissions } = props +export const GlobalRoutes: React.FC = (props) => { + const { global, permissions } = props - const match = useRouteMatch() + const match = useRouteMatch() - const { - routes: { admin: adminRoute }, - } = useConfig() + const { + routes: { admin: adminRoute }, + } = useConfig() - const { user } = useAuth() + const { user } = useAuth() - return ( - - - {permissions?.readVersions?.permission ? ( - - ) : ( - - )} - - - {permissions?.readVersions?.permission ? ( - - ) : ( - - )} - - - - - {globalCustomRoutes({ - global, - match, - permissions, - user, - })} - - - - - - - - ) - } - - return null + return ( + + + {permissions?.readVersions?.permission ? ( + + ) : ( + + )} + + + {permissions?.readVersions?.permission ? ( + + ) : ( + + )} + + + + + {globalCustomRoutes({ + global, + match, + permissions, + user, + })} + + + + + + + + ) } diff --git a/packages/payload/src/admin/components/views/Global/index.tsx b/packages/payload/src/admin/components/views/Global/index.tsx index ab693e0e8..1e0a530f8 100644 --- a/packages/payload/src/admin/components/views/Global/index.tsx +++ b/packages/payload/src/admin/components/views/Global/index.tsx @@ -3,27 +3,25 @@ import { useTranslation } from 'react-i18next' import { useLocation } from 'react-router-dom' import type { Fields } from '../../forms/Form/types' +import type { GlobalEditViewProps } from '../types' import type { IndexProps } from './types' import usePayloadAPI from '../../../hooks/usePayloadAPI' -import { useStepNav } from '../../elements/StepNav' import buildStateFromSchema from '../../forms/Form/buildStateFromSchema' import { useAuth } from '../../utilities/Auth' import { useConfig } from '../../utilities/Config' 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' -import { EditDepthContext } from '../../utilities/EditDepth' -import { EditViewProps } from '../types' const GlobalView: React.FC = (props) => { const { global } = props const { state: locationState } = useLocation<{ data?: Record }>() const { code: locale } = useLocale() - const { setStepNav } = useStepNav() const { permissions, user } = useAuth() const [initialState, setInitialState] = useState() const [updatedAt, setUpdatedAt] = useState() @@ -37,12 +35,7 @@ const GlobalView: React.FC = (props) => { serverURL, } = useConfig() - const { - admin: { components: { views: { Edit: Edit } = {} } = {} } = {}, - fields, - label, - slug, - } = global + const { admin: { components: { views: { Edit: Edit } = {} } = {} } = {}, fields, slug } = global const onSave = useCallback( async (json) => { @@ -71,16 +64,6 @@ const GlobalView: React.FC = (props) => { const dataToRender = locationState?.data || data - useEffect(() => { - const nav = [ - { - label, - }, - ] - - setStepNav(nav) - }, [setStepNav, label]) - useEffect(() => { const awaitInitialState = async () => { const preferences = await getDocPreferences() @@ -102,7 +85,7 @@ const GlobalView: React.FC = (props) => { const isLoading = !initialState || !docPermissions || isLoadingData - const componentProps: EditViewProps = { + const componentProps: GlobalEditViewProps = { action: `${serverURL}${api}/globals/${slug}?locale=${locale}&fallback-locale=null`, apiURL: `${serverURL}${api}/globals/${slug}?locale=${locale}${ global.versions?.drafts ? '&draft=true' : '' diff --git a/packages/payload/src/admin/components/views/LivePreview/index.tsx b/packages/payload/src/admin/components/views/LivePreview/index.tsx index d89e6a57c..b5c3ceb1b 100644 --- a/packages/payload/src/admin/components/views/LivePreview/index.tsx +++ b/packages/payload/src/admin/components/views/LivePreview/index.tsx @@ -12,6 +12,7 @@ import { filterFields } from '../../forms/RenderFields/filterFields' import { fieldTypes } from '../../forms/field-types' import LeaveWithoutSaving from '../../modals/LeaveWithoutSaving' import Meta from '../../utilities/Meta' +import { SetStepNav } from '../collections/Edit/SetStepNav' import { LivePreview } from './Preview' import './index.scss' import { usePopupWindow } from './usePopupWindow' @@ -71,6 +72,7 @@ export const LivePreviewView: React.FC = (props) => { return ( + = ({ collection, global }) => { ? originalDocFetchURL : `${compareBaseURL}/${compareValue.value}` - const [{ data: doc, isError, isLoading: isLoadingData }] = usePayloadAPI(versionFetchURL, { + const [{ data: doc, isError }] = usePayloadAPI(versionFetchURL, { initialParams: { depth: 1, locale: '*' }, }) const [{ data: publishedDoc }] = usePayloadAPI(originalDocFetchURL, { diff --git a/packages/payload/src/admin/components/views/collections/Edit/Default.tsx b/packages/payload/src/admin/components/views/collections/Edit/Default.tsx index 675b01504..c25aa0b58 100644 --- a/packages/payload/src/admin/components/views/collections/Edit/Default.tsx +++ b/packages/payload/src/admin/components/views/collections/Edit/Default.tsx @@ -1,6 +1,8 @@ import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' +import type { CollectionEditViewProps } from '../../types' + import { getTranslation } from '../../../../../utilities/getTranslation' import { DocumentHeader } from '../../../elements/DocumentHeader' import { FormLoadingOverlayToggle } from '../../../elements/Loading' @@ -9,104 +11,99 @@ import { useAuth } from '../../../utilities/Auth' import { OperationContext } from '../../../utilities/OperationProvider' import { CollectionRoutes } from './Routes' import { CustomCollectionComponent } from './Routes/CustomComponent' -import { EditViewProps } from '../../types' import './index.scss' const baseClass = 'collection-edit' const DefaultEditView: React.FC< - EditViewProps & { - disableRoutes?: boolean + CollectionEditViewProps & { customHeader?: React.ReactNode + disableRoutes?: boolean } > = (props) => { const { i18n } = useTranslation('general') const { refreshCookieAsync, user } = useAuth() - if ('collection' in props) { - const { - id, - action, - apiURL, - collection, - customHeader, - data, - disableRoutes, - hasSavePermission, - internalState, - isEditing, - isLoading, - onSave: onSaveFromProps, - } = props + const { + id, + action, + apiURL, + collection, + customHeader, + data, + disableRoutes, + hasSavePermission, + internalState, + isEditing, + isLoading, + onSave: onSaveFromProps, + } = 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( - async (json) => { - if (auth && id === user.id) { - await refreshCookieAsync() - } + const onSave = useCallback( + async (json) => { + if (auth && id === user.id) { + await refreshCookieAsync() + } - if (typeof onSaveFromProps === 'function') { - onSaveFromProps({ - ...json, - operation: id ? 'update' : 'create', - }) - } - }, - [id, onSaveFromProps, auth, user, refreshCookieAsync], - ) + if (typeof onSaveFromProps === 'function') { + onSaveFromProps({ + ...json, + operation: id ? 'update' : 'create', + }) + } + }, + [id, onSaveFromProps, auth, user, refreshCookieAsync], + ) - const operation = isEditing ? 'update' : 'create' + const operation = isEditing ? 'update' : 'create' - return ( -
- -
- - {!isLoading && ( - - - {disableRoutes ? ( - - ) : ( - - )} - - )} - -
-
- ) - } - - return null + return ( +
+ +
+ + {!isLoading && ( + + + {disableRoutes ? ( + + ) : ( + + )} + + )} + +
+
+ ) } export default DefaultEditView diff --git a/packages/payload/src/admin/components/views/collections/Edit/Default/index.tsx b/packages/payload/src/admin/components/views/collections/Edit/Default/index.tsx index bf3953de0..3b92781b9 100644 --- a/packages/payload/src/admin/components/views/collections/Edit/Default/index.tsx +++ b/packages/payload/src/admin/components/views/collections/Edit/Default/index.tsx @@ -1,6 +1,8 @@ import React, { Fragment } from 'react' import { useTranslation } from 'react-i18next' +import type { CollectionEditViewProps } from '../../../types' + import { getTranslation } from '../../../../../../utilities/getTranslation' import { DocumentControls } from '../../../../elements/DocumentControls' import { Gutter } from '../../../../elements/Gutter' @@ -13,115 +15,110 @@ import Auth from '../Auth' import { SetStepNav } from '../SetStepNav' import Upload from '../Upload' import './index.scss' -import { EditViewProps } from '../../../types' const baseClass = 'collection-default-edit' -export const DefaultCollectionEdit: React.FC = (props) => { - if ('collection' in props) { - const { i18n, t } = useTranslation('general') +export const DefaultCollectionEdit: React.FC = (props) => { + const { i18n, t } = useTranslation('general') - const { - id, - apiURL, - collection, - data, - disableActions, - disableLeaveWithoutSaving, - hasSavePermission, - internalState, - isEditing, - permissions, - } = props + const { + id, + apiURL, + collection, + data, + disableActions, + disableLeaveWithoutSaving, + hasSavePermission, + internalState, + isEditing, + permissions, + } = props - const { auth, fields, upload } = collection + const { auth, fields, upload } = collection - const operation = isEditing ? 'update' : 'create' + const operation = isEditing ? 'update' : 'create' - const sidebarFields = filterFields({ - fieldSchema: fields, - fieldTypes, - filter: (field) => field?.admin?.position === 'sidebar', - permissions: permissions.fields, - readOnly: !hasSavePermission, - }) + const sidebarFields = filterFields({ + fieldSchema: fields, + fieldTypes, + filter: (field) => field?.admin?.position === 'sidebar', + permissions: permissions.fields, + readOnly: !hasSavePermission, + }) - const hasSidebar = sidebarFields && sidebarFields.length > 0 + const hasSidebar = sidebarFields && sidebarFields.length > 0 - return ( - - - -
-
- - {!(collection.versions?.drafts && collection.versions?.drafts?.autosave) && - !disableLeaveWithoutSaving && } - - {auth && ( - - )} - {upload && ( - - )} - !field?.admin?.position || field?.admin?.position !== 'sidebar'} - permissions={permissions.fields} + return ( + + + +
+
+ + {!(collection.versions?.drafts && collection.versions?.drafts?.autosave) && + !disableLeaveWithoutSaving && } + + {auth && ( + - -
- {hasSidebar && ( -
-
-
-
- -
+ )} + {upload && } + !field?.admin?.position || field?.admin?.position !== 'sidebar'} + permissions={permissions.fields} + readOnly={!hasSavePermission} + /> + +
+ {hasSidebar && ( +
+
+
+
+
- )} -
- - ) - } +
+ )} +
+ + ) } diff --git a/packages/payload/src/admin/components/views/collections/Edit/Routes/CustomComponent.tsx b/packages/payload/src/admin/components/views/collections/Edit/Routes/CustomComponent.tsx index 393abedc3..3ceae22d3 100644 --- a/packages/payload/src/admin/components/views/collections/Edit/Routes/CustomComponent.tsx +++ b/packages/payload/src/admin/components/views/collections/Edit/Routes/CustomComponent.tsx @@ -1,12 +1,12 @@ import React from 'react' -import type { EditViewProps } from '../../../types' +import type { CollectionEditViewProps } from '../../../types' +import { LivePreviewView } from '../../../LivePreview' import { QueryInspector } from '../../../RestAPI' import VersionView from '../../../Version/Version' import VersionsView from '../../../Versions' import { DefaultCollectionEdit } from '../Default/index' -import { LivePreviewView } from '../../../LivePreview' export type collectionViewType = | 'API' @@ -30,37 +30,33 @@ export const defaultCollectionViews: { } export const CustomCollectionComponent = ( - args: EditViewProps & { + args: CollectionEditViewProps & { 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 - // Need to cascade through the hierarchy to find the correct component to render - // For example, the Edit view: - // 1. Edit?.Default - // 2. Edit?.Default?.Component - // 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 - const Component = - typeof Edit === 'object' && typeof Edit[view] === 'function' - ? Edit[view] - : typeof Edit === 'object' && - typeof Edit?.[view] === 'object' && - // @ts-ignore - typeof Edit[view].Component === 'function' - ? // @ts-ignore - Edit[view].Component - : defaultCollectionViews[view] + // Overriding components may come from multiple places in the config + // Need to cascade through the hierarchy to find the correct component to render + // For example, the Edit view: + // 1. Edit?.Default + // 2. Edit?.Default?.Component + // 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 + const Component = + typeof Edit === 'object' && typeof Edit[view] === 'function' + ? Edit[view] + : typeof Edit === 'object' && + typeof Edit?.[view] === 'object' && + // @ts-ignore + typeof Edit[view].Component === 'function' + ? // @ts-ignore + Edit[view].Component + : defaultCollectionViews[view] - if (Component) { - return - } + if (Component) { + return } - - return null } diff --git a/packages/payload/src/admin/components/views/collections/Edit/Routes/index.tsx b/packages/payload/src/admin/components/views/collections/Edit/Routes/index.tsx index fad0d8871..69bb03a25 100644 --- a/packages/payload/src/admin/components/views/collections/Edit/Routes/index.tsx +++ b/packages/payload/src/admin/components/views/collections/Edit/Routes/index.tsx @@ -2,7 +2,7 @@ import { lazy } from 'react' import React from 'react' import { Route, Switch, useRouteMatch } from 'react-router-dom' -import type { EditViewProps } from '../../../types' +import type { CollectionEditViewProps } from '../../../types' import { useAuth } from '../../../../utilities/Auth' import { useConfig } from '../../../../utilities/Config' @@ -13,77 +13,71 @@ import { collectionCustomRoutes } from './custom' // @ts-expect-error Just TypeScript being broken // TODO: Open TypeScript issue const Unauthorized = lazy(() => import('../../../Unauthorized')) -export const CollectionRoutes: React.FC = (props) => { - if ('collection' in props) { - const { collection, permissions } = props +export const CollectionRoutes: React.FC = (props) => { + const { collection, permissions } = props - const match = useRouteMatch() + const match = useRouteMatch() - const { - routes: { admin: adminRoute }, - } = useConfig() + const { + routes: { admin: adminRoute }, + } = useConfig() - const { user } = useAuth() + const { user } = useAuth() - return ( - - - {permissions?.readVersions?.permission ? ( - - ) : ( - - )} - - - {permissions?.read ? ( - - ) : ( - - )} - - - {permissions?.readVersions?.permission ? ( - - ) : ( - - )} - - - - - {collectionCustomRoutes({ - collection, - match, - permissions, - user, - })} - - - - - - - - ) - } + return ( + + + {permissions?.readVersions?.permission ? ( + + ) : ( + + )} + + + {permissions?.read ? : } + + + {permissions?.readVersions?.permission ? ( + + ) : ( + + )} + + + + + {collectionCustomRoutes({ + collection, + match, + permissions, + user, + })} + + + + + + + + ) } diff --git a/packages/payload/src/admin/components/views/collections/Edit/SetStepNav.tsx b/packages/payload/src/admin/components/views/collections/Edit/SetStepNav.tsx index 0610a905a..62daa389d 100644 --- a/packages/payload/src/admin/components/views/collections/Edit/SetStepNav.tsx +++ b/packages/payload/src/admin/components/views/collections/Edit/SetStepNav.tsx @@ -2,6 +2,7 @@ import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import type { SanitizedCollectionConfig } from '../../../../../collections/config/types' +import type { SanitizedGlobalConfig } from '../../../../../exports/types' import type { StepNavItem } from '../../../elements/StepNav/types' import { getTranslation } from '../../../../../utilities/getTranslation' @@ -9,45 +10,96 @@ import useTitle from '../../../../hooks/useTitle' import { useStepNav } from '../../../elements/StepNav' import { useConfig } from '../../../utilities/Config' -export const SetStepNav: React.FC<{ - collection: SanitizedCollectionConfig - id: string - isEditing: boolean -}> = ({ id, collection, isEditing }) => { - const { - admin: { useAsTitle }, - labels: { plural: pluralLabel }, - slug, - } = collection +export const SetStepNav: React.FC< + | { + collection: SanitizedCollectionConfig + id: string + isEditing: boolean + } + | { + global: SanitizedGlobalConfig + } +> = (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 { i18n, t } = useTranslation('general') + const { routes: { admin }, } = useConfig() - const title = useTitle(collection) - useEffect(() => { - const nav: StepNavItem[] = [ - { + const nav: StepNavItem[] = [] + + if (collection) { + nav.push({ label: getTranslation(pluralLabel, i18n), 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({ - label: t('createNew'), + label: getTranslation(global.label, i18n), + url: `${admin}/globals/${slug}`, }) } 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 } diff --git a/packages/payload/src/admin/components/views/collections/Edit/index.tsx b/packages/payload/src/admin/components/views/collections/Edit/index.tsx index 16993d866..ed8ddb08b 100644 --- a/packages/payload/src/admin/components/views/collections/Edit/index.tsx +++ b/packages/payload/src/admin/components/views/collections/Edit/index.tsx @@ -4,6 +4,7 @@ import { useHistory, useRouteMatch } from 'react-router-dom' import type { CollectionPermission } from '../../../../../auth' import type { Fields } from '../../../forms/Form/types' +import type { CollectionEditViewProps } from '../../types' import type { IndexProps } from './types' import usePayloadAPI from '../../../../hooks/usePayloadAPI' @@ -17,7 +18,6 @@ import RenderCustomComponent from '../../../utilities/RenderCustomComponent' import NotFound from '../../NotFound' import DefaultEdit from './Default' import formatFields from './formatFields' -import { EditViewProps } from '../../types' const EditView: React.FC = (props) => { const { collection: incomingCollection, isEditing } = props @@ -125,7 +125,7 @@ const EditView: React.FC = (props) => { const isLoading = !internalState || !docPermissions || isLoadingData - const componentProps: EditViewProps = { + const componentProps: CollectionEditViewProps = { id, action, apiURL, diff --git a/packages/payload/src/admin/components/views/types.ts b/packages/payload/src/admin/components/views/types.ts index 6e430e38a..f9e2a8d87 100644 --- a/packages/payload/src/admin/components/views/types.ts +++ b/packages/payload/src/admin/components/views/types.ts @@ -5,24 +5,25 @@ import type { SanitizedGlobalConfig, } from '../../../exports/types' -export type EditViewProps = ( - | { - collection: SanitizedCollectionConfig - disableActions?: boolean - disableLeaveWithoutSaving?: boolean - hasSavePermission: boolean - id: string - initialState?: Fields - internalState: Fields - isEditing: boolean - permissions: CollectionPermission - } - | { - global: SanitizedGlobalConfig - initialState: Fields - permissions: GlobalPermission - } -) & { +export type CollectionEditViewProps = BaseEditViewProps & { + collection: SanitizedCollectionConfig + disableActions?: boolean + disableLeaveWithoutSaving?: boolean + hasSavePermission: boolean + id: string + initialState?: Fields + internalState: Fields + isEditing: boolean + permissions: CollectionPermission +} + +export type GlobalEditViewProps = BaseEditViewProps & { + global: SanitizedGlobalConfig + initialState: Fields + permissions: GlobalPermission +} + +export type BaseEditViewProps = { action: string apiURL: string canAccessAdmin: boolean @@ -32,3 +33,5 @@ export type EditViewProps = ( updatedAt: string user: User } + +export type EditViewProps = CollectionEditViewProps | GlobalEditViewProps diff --git a/packages/payload/src/admin/hooks/useTitle.tsx b/packages/payload/src/admin/hooks/useTitle.tsx index 63f24f09f..2d30064aa 100644 --- a/packages/payload/src/admin/hooks/useTitle.tsx +++ b/packages/payload/src/admin/hooks/useTitle.tsx @@ -52,11 +52,20 @@ export const formatUseAsTitle = (args: { 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 field = useFormFields(([formFields]) => formFields[collection?.admin?.useAsTitle]) + + const field = useFormFields(([formFields]) => { + if (!collection) return + return formFields[collection?.admin?.useAsTitle] + }) + const config = useConfig() + if (!collection) return '' + return formatUseAsTitle({ collection, config, field, i18n }) } diff --git a/packages/payload/src/config/types.ts b/packages/payload/src/config/types.ts index ae51cd096..05f0870cc 100644 --- a/packages/payload/src/config/types.ts +++ b/packages/payload/src/config/types.ts @@ -13,7 +13,7 @@ import type { InlineConfig } from 'vite' import type { DocumentTab } from '../admin/components/elements/DocumentHeader/Tabs/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 { PayloadBundler } from '../bundlers/types' import type { @@ -241,7 +241,7 @@ export type EditViewConfig = { path: string } -export type EditViewComponent = React.ComponentType +export type EditViewComponent = React.ComponentType export type EditView = EditViewComponent | EditViewConfig diff --git a/test/admin/components/views/CustomDefault/index.tsx b/test/admin/components/views/CustomDefault/index.tsx index 05ac9af0b..f210cfb92 100644 --- a/test/admin/components/views/CustomDefault/index.tsx +++ b/test/admin/components/views/CustomDefault/index.tsx @@ -1,9 +1,10 @@ import React, { Fragment, useEffect } from 'react' 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 { useConfig } from '../../../../../packages/payload/src/admin/components/utilities/Config' -import { EditViewComponent } from '../../../../../packages/payload/src/config/types' const CustomDefaultView: EditViewComponent = ({ canAccessAdmin, diff --git a/test/admin/components/views/CustomEdit/index.tsx b/test/admin/components/views/CustomEdit/index.tsx index 0f793bc14..2f2b9e05a 100644 --- a/test/admin/components/views/CustomEdit/index.tsx +++ b/test/admin/components/views/CustomEdit/index.tsx @@ -1,9 +1,10 @@ import React, { Fragment, useEffect } from 'react' 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 { useConfig } from '../../../../../packages/payload/src/admin/components/utilities/Config' -import { EditViewComponent } from '../../../../../packages/payload/src/config/types' const CustomEditView: EditViewComponent = ({ canAccessAdmin, diff --git a/test/admin/components/views/CustomVersions/index.tsx b/test/admin/components/views/CustomVersions/index.tsx index 24694d010..872941da1 100644 --- a/test/admin/components/views/CustomVersions/index.tsx +++ b/test/admin/components/views/CustomVersions/index.tsx @@ -1,9 +1,10 @@ import React, { Fragment, useEffect } from 'react' 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 { useConfig } from '../../../../../packages/payload/src/admin/components/utilities/Config' -import { EditViewComponent } from '../../../../../packages/payload/src/config/types' const CustomVersionsView: EditViewComponent = ({ canAccessAdmin, diff --git a/test/live-preview/config.ts b/test/live-preview/config.ts index 64a48a755..3203aae7a 100644 --- a/test/live-preview/config.ts +++ b/test/live-preview/config.ts @@ -19,7 +19,7 @@ export default buildConfigWithDefaults({ slug: 'users', auth: true, admin: { - useAsTitle: 'email', + useAsTitle: 'title', }, fields: [], }, diff --git a/test/live-preview/payload-types.ts b/test/live-preview/payload-types.ts index f374be5f7..0b67db742 100644 --- a/test/live-preview/payload-types.ts +++ b/test/live-preview/payload-types.ts @@ -9,21 +9,11 @@ export interface Config { collections: { users: User - 'hidden-collection': HiddenCollection - posts: Post - 'group-one-collection-ones': GroupOneCollectionOne - 'group-one-collection-twos': GroupOneCollectionTwo - 'group-two-collection-ones': GroupTwoCollectionOne - 'group-two-collection-twos': GroupTwoCollectionTwo + pages: Page 'payload-preferences': PayloadPreference 'payload-migrations': PayloadMigration } - globals: { - 'hidden-global': HiddenGlobal - global: Global - 'group-globals-one': GroupGlobalsOne - 'group-globals-two': GroupGlobalsTwo - } + globals: {} } export interface User { id: string @@ -38,52 +28,19 @@ export interface User { lockUntil?: string password?: string } -export interface HiddenCollection { +export interface Page { id: string - title?: string - updatedAt: string - createdAt: 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 + title: string + description: string + slug: string updatedAt: string createdAt: string } export interface PayloadPreference { id: string user: { - value: string | User relationTo: 'users' + value: string | User } key?: string value?: @@ -114,27 +71,14 @@ export interface PayloadMigration { updatedAt: string createdAt: string } -export interface HiddenGlobal { - id: string - title?: string - updatedAt?: string - createdAt?: string -} -export interface Global { - id: string - 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 + +declare module 'payload' { + export interface GeneratedTypes { + collections: { + users: User + pages: Page + 'payload-preferences': PayloadPreference + 'payload-migrations': PayloadMigration + } + } }