From d61eef23d110d58c666ad49233a756c31ca7646d Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Wed, 27 Sep 2023 10:56:15 -0400 Subject: [PATCH] chore: fixes admin view types (#3398) --- packages/payload/components/views/Edit.d.ts | 1 - packages/payload/components/views/Edit.js | 2 +- .../elements/DocumentDrawer/types.ts | 3 +- .../DocumentHeader/Tabs/getCustomViews.ts | 7 +- .../field-types/Relationship/AddNew/index.tsx | 2 +- .../components/views/Account/Default.tsx | 206 +++++++++--------- .../admin/components/views/Global/Default.tsx | 101 +++++---- .../components/views/Global/Default/index.tsx | 155 ++++++------- .../views/Global/Routes/CustomComponent.tsx | 45 ++-- .../components/views/Global/Routes/index.tsx | 96 ++++---- .../admin/components/views/Global/index.tsx | 37 ++-- .../admin/components/views/Global/types.ts | 17 -- .../views/collections/Edit/Default.tsx | 166 +++++++------- .../views/collections/Edit/Default/index.tsx | 188 ++++++++-------- .../Edit/Routes/CustomComponent.tsx | 45 ++-- .../views/collections/Edit/Routes/index.tsx | 102 ++++----- .../views/collections/Edit/index.tsx | 35 +-- .../views/collections/Edit/types.ts | 30 --- .../src/admin/components/views/types.ts | 28 +++ .../payload/src/collections/config/types.ts | 48 ++-- packages/payload/src/config/types.ts | 16 ++ .../src/exports/components/views/Edit.ts | 1 - packages/payload/src/globals/config/types.ts | 49 ++--- .../components/views/CustomDefault/index.tsx | 4 +- .../components/views/CustomEdit/index.tsx | 4 +- .../components/views/CustomVersions/index.tsx | 4 +- .../components/views/CustomView/index.tsx | 4 +- test/admin/config.ts | 1 + 28 files changed, 718 insertions(+), 679 deletions(-) create mode 100644 packages/payload/src/admin/components/views/types.ts diff --git a/packages/payload/components/views/Edit.d.ts b/packages/payload/components/views/Edit.d.ts index a87f9cb3d..21340ba1e 100644 --- a/packages/payload/components/views/Edit.d.ts +++ b/packages/payload/components/views/Edit.d.ts @@ -1,3 +1,2 @@ export { default as Edit } from '../../dist/admin/components/views/collections/Edit/Default'; -export type { Props } from '../../dist/admin/components/views/collections/Edit/types'; //# sourceMappingURL=Edit.d.ts.map \ No newline at end of file diff --git a/packages/payload/components/views/Edit.js b/packages/payload/components/views/Edit.js index 2725b53ae..c01afd2af 100644 --- a/packages/payload/components/views/Edit.js +++ b/packages/payload/components/views/Edit.js @@ -15,4 +15,4 @@ function _interop_require_default(obj) { }; } -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9leHBvcnRzL2NvbXBvbmVudHMvdmlld3MvRWRpdC50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgeyBkZWZhdWx0IGFzIEVkaXQgfSBmcm9tICcuLi8uLi8uLi9hZG1pbi9jb21wb25lbnRzL3ZpZXdzL2NvbGxlY3Rpb25zL0VkaXQvRGVmYXVsdCdcbmV4cG9ydCB0eXBlIHsgUHJvcHMgfSBmcm9tICcuLi8uLi8uLi9hZG1pbi9jb21wb25lbnRzL3ZpZXdzL2NvbGxlY3Rpb25zL0VkaXQvdHlwZXMnXG4iXSwibmFtZXMiOlsiRWRpdCJdLCJtYXBwaW5ncyI6Ijs7OzsrQkFBb0JBOzs7ZUFBQUEsZ0JBQUk7OztnRUFBUSJ9 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9leHBvcnRzL2NvbXBvbmVudHMvdmlld3MvRWRpdC50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgeyBkZWZhdWx0IGFzIEVkaXQgfSBmcm9tICcuLi8uLi8uLi9hZG1pbi9jb21wb25lbnRzL3ZpZXdzL2NvbGxlY3Rpb25zL0VkaXQvRGVmYXVsdCdcbiJdLCJuYW1lcyI6WyJFZGl0Il0sIm1hcHBpbmdzIjoiOzs7OytCQUFvQkE7OztlQUFBQSxnQkFBSTs7O2dFQUFRIn0= \ No newline at end of file diff --git a/packages/payload/src/admin/components/elements/DocumentDrawer/types.ts b/packages/payload/src/admin/components/elements/DocumentDrawer/types.ts index 3e2dabf08..6ef718889 100644 --- a/packages/payload/src/admin/components/elements/DocumentDrawer/types.ts +++ b/packages/payload/src/admin/components/elements/DocumentDrawer/types.ts @@ -1,7 +1,6 @@ import type React from 'react' import type { HTMLAttributes } from 'react' - -import type { Props as EditViewProps } from '../../views/collections/Edit/types' +import { EditViewProps } from '../../views/types' export type DocumentDrawerProps = { collectionSlug: string diff --git a/packages/payload/src/admin/components/elements/DocumentHeader/Tabs/getCustomViews.ts b/packages/payload/src/admin/components/elements/DocumentHeader/Tabs/getCustomViews.ts index 908e9ad07..5032a1aea 100644 --- a/packages/payload/src/admin/components/elements/DocumentHeader/Tabs/getCustomViews.ts +++ b/packages/payload/src/admin/components/elements/DocumentHeader/Tabs/getCustomViews.ts @@ -1,6 +1,5 @@ -import type { CollectionEditViewConfig } from '../../../../../collections/config/types' +import { EditViewComponent, EditViewConfig } from '../../../../../exports/config' import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from '../../../../../exports/types' -import type { GlobalEditViewConfig } from '../../../../../globals/config/types' import { defaultGlobalViews } from '../../../views/Global/Routes/CustomComponent' import { defaultCollectionViews } from '../../../views/collections/Edit/Routes/CustomComponent' @@ -8,10 +7,10 @@ import { defaultCollectionViews } from '../../../views/collections/Edit/Routes/C export const getCustomViews = (args: { collection: SanitizedCollectionConfig global: SanitizedGlobalConfig -}): (CollectionEditViewConfig | GlobalEditViewConfig)[] => { +}): EditViewConfig[] => { const { collection, global } = args - let customViews: (CollectionEditViewConfig | GlobalEditViewConfig)[] + let customViews: EditViewConfig[] if (collection) { const collectionViewsConfig = diff --git a/packages/payload/src/admin/components/forms/field-types/Relationship/AddNew/index.tsx b/packages/payload/src/admin/components/forms/field-types/Relationship/AddNew/index.tsx index c69e4f91f..a34efa17b 100644 --- a/packages/payload/src/admin/components/forms/field-types/Relationship/AddNew/index.tsx +++ b/packages/payload/src/admin/components/forms/field-types/Relationship/AddNew/index.tsx @@ -2,7 +2,6 @@ import React, { Fragment, useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import type { SanitizedCollectionConfig } from '../../../../../../collections/config/types' -import type { Props as EditViewProps } from '../../../../views/collections/Edit/types' import type { Value } from '../types' import type { Props } from './types' @@ -16,6 +15,7 @@ import { useAuth } from '../../../../utilities/Auth' import { useConfig } from '../../../../utilities/Config' import './index.scss' import { useRelatedCollections } from './useRelatedCollections' +import { EditViewProps } from '../../../../views/types' const baseClass = 'relationship-add-new' diff --git a/packages/payload/src/admin/components/views/Account/Default.tsx b/packages/payload/src/admin/components/views/Account/Default.tsx index 0d123180d..22de87fbe 100644 --- a/packages/payload/src/admin/components/views/Account/Default.tsx +++ b/packages/payload/src/admin/components/views/Account/Default.tsx @@ -2,7 +2,6 @@ import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' import type { Translation } from '../../../../translations/type' -import type { Props } from './types' import { DocumentControls } from '../../elements/DocumentControls' import { DocumentHeader } from '../../elements/DocumentHeader' @@ -20,125 +19,132 @@ 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) => { - const { - action, - apiURL, - collection, - data, - hasSavePermission, - initialState, - isLoading, - onSave: onSaveFromProps, - permissions, - } = props +const DefaultAccount: React.FC = (props) => { + if ('collection' in 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 724325503..9f965e604 100644 --- a/packages/payload/src/admin/components/views/Global/Default.tsx +++ b/packages/payload/src/admin/components/views/Global/Default.tsx @@ -1,8 +1,6 @@ import React from 'react' import { useTranslation } from 'react-i18next' -import type { Props } from './types' - import { getTranslation } from '../../../../utilities/getTranslation' import { DocumentHeader } from '../../elements/DocumentHeader' import { FormLoadingOverlayToggle } from '../../elements/Loading' @@ -11,58 +9,67 @@ 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 = (props) => { - const { - action, - apiURL, - data, - disableRoutes, - global, - initialState, - isLoading, - onSave, - permissions, - } = props +const DefaultGlobalView: React.FC< + EditViewProps & { + 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 { label } = global + const { label } = global - const hasSavePermission = permissions?.update?.permission + const hasSavePermission = permissions?.update?.permission - return ( -
- -
- - {!isLoading && ( - - - {disableRoutes ? ( - - ) : ( - - )} - - )} - -
-
- ) + return ( +
+ +
+ + {!isLoading && ( + + + {disableRoutes ? ( + + ) : ( + + )} + + )} + +
+
+ ) + } + + return null } 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 10de1f729..b38c5391d 100644 --- a/packages/payload/src/admin/components/views/Global/Default/index.tsx +++ b/packages/payload/src/admin/components/views/Global/Default/index.tsx @@ -1,8 +1,6 @@ import React from 'react' import { useTranslation } from 'react-i18next' -import type { Props } from '../types' - import { getTranslation } from '../../../../../utilities/getTranslation' import { DocumentControls } from '../../../elements/DocumentControls' import { Gutter } from '../../../elements/Gutter' @@ -13,91 +11,96 @@ import { fieldTypes } from '../../../forms/field-types' import LeaveWithoutSaving from '../../../modals/LeaveWithoutSaving' import Meta from '../../../utilities/Meta' import './index.scss' +import { EditViewProps } from '../../types' const baseClass = 'global-edit' -export const DefaultGlobalEdit: React.FC = (props) => { - const { apiURL, data, global, permissions } = props +export const DefaultGlobalEdit: React.FC = (props) => { + if ('global' in props) { + const { apiURL, data, global, permissions } = props - const { i18n } = useTranslation('general') + const { i18n } = useTranslation('general') - 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} + return ( + + {/* */} + +
+
+ - -
- {hasSidebar && ( -
-
-
-
- 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} + /> +
-
- )} -
- - ) + )} +
+ + ) + } + + 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 bcef37fb1..9c9959684 100644 --- a/packages/payload/src/admin/components/views/Global/Routes/CustomComponent.tsx +++ b/packages/payload/src/admin/components/views/Global/Routes/CustomComponent.tsx @@ -1,10 +1,9 @@ import React from 'react' -import type { Props } from '../types' - import VersionView from '../../Version/Version' import VersionsView from '../../Versions' import { DefaultGlobalEdit } from '../Default/index' +import { EditViewProps } from '../../types' export type globalViewType = | 'API' @@ -28,30 +27,36 @@ export const defaultGlobalViews: { } export const CustomGlobalComponent = ( - args: Props & { + args: EditViewProps & { view: globalViewType }, ) => { - const { global, view } = args + if ('global' in 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 - const Component = - typeof Edit === 'object' && typeof Edit[view] === 'function' - ? Edit[view] - : typeof Edit === 'object' && - typeof Edit?.[view] === 'object' && - typeof Edit[view].Component === 'function' - ? 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 8852ea3d8..185ab0cef 100644 --- a/packages/payload/src/admin/components/views/Global/Routes/index.tsx +++ b/packages/payload/src/admin/components/views/Global/Routes/index.tsx @@ -5,60 +5,64 @@ import { Route, Switch, useRouteMatch } from 'react-router-dom' import { useAuth } from '../../../utilities/Auth' import { useConfig } from '../../../utilities/Config' import NotFound from '../../NotFound' -import { type Props } from '../types' 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) => { - const { global, permissions } = props +export const GlobalRoutes: React.FC = (props) => { + if ('global' in 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 ( + + + {permissions?.readVersions?.permission ? ( + + ) : ( + + )} + + + {permissions?.readVersions?.permission ? ( + + ) : ( + + )} + + {globalCustomRoutes({ + global, + match, + permissions, + user, + })} + + + + + + + + ) + } + + return null } diff --git a/packages/payload/src/admin/components/views/Global/index.tsx b/packages/payload/src/admin/components/views/Global/index.tsx index adf621f79..ab693e0e8 100644 --- a/packages/payload/src/admin/components/views/Global/index.tsx +++ b/packages/payload/src/admin/components/views/Global/index.tsx @@ -16,8 +16,11 @@ 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() @@ -34,8 +37,6 @@ const GlobalView: React.FC = (props) => { serverURL, } = useConfig() - const { global } = props - const { admin: { components: { views: { Edit: Edit } = {} } = {} } = {}, fields, @@ -101,26 +102,28 @@ const GlobalView: React.FC = (props) => { const isLoading = !initialState || !docPermissions || isLoadingData + const componentProps: EditViewProps = { + action: `${serverURL}${api}/globals/${slug}?locale=${locale}&fallback-locale=null`, + apiURL: `${serverURL}${api}/globals/${slug}?locale=${locale}${ + global.versions?.drafts ? '&draft=true' : '' + }`, + canAccessAdmin: permissions?.canAccessAdmin, + data: dataToRender, + global, + initialState, + isLoading, + onSave, + permissions: docPermissions, + updatedAt: updatedAt || dataToRender?.updatedAt, + user, + } + return ( ) diff --git a/packages/payload/src/admin/components/views/Global/types.ts b/packages/payload/src/admin/components/views/Global/types.ts index 0c6a5cd30..81a7208ea 100644 --- a/packages/payload/src/admin/components/views/Global/types.ts +++ b/packages/payload/src/admin/components/views/Global/types.ts @@ -1,22 +1,5 @@ -import type { GlobalPermission } from '../../../../auth/types' import type { SanitizedGlobalConfig } from '../../../../globals/config/types' -import type { Document } from '../../../../types' -import type { Fields } from '../../forms/Form/types' export type IndexProps = { global: SanitizedGlobalConfig } - -export type Props = { - action: string - apiURL: string - autosaveEnabled: boolean - data: Document - disableRoutes?: boolean - global: SanitizedGlobalConfig - initialState: Fields - isLoading: boolean - onSave: () => void - permissions: GlobalPermission - updatedAt: string -} 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 deb90e46d..492185d25 100644 --- a/packages/payload/src/admin/components/views/collections/Edit/Default.tsx +++ b/packages/payload/src/admin/components/views/collections/Edit/Default.tsx @@ -1,8 +1,6 @@ import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import type { Props } from './types' - import { getTranslation } from '../../../../../utilities/getTranslation' import { DocumentHeader } from '../../../elements/DocumentHeader' import { FormLoadingOverlayToggle } from '../../../elements/Loading' @@ -12,95 +10,105 @@ import { OperationContext } from '../../../utilities/OperationProvider' import { CollectionRoutes } from './Routes' import { CustomCollectionComponent } from './Routes/CustomComponent' import './index.scss' +import { EditViewProps } from '../../types' const baseClass = 'collection-edit' -const DefaultEditView: React.FC = (props) => { +const DefaultEditView: React.FC< + EditViewProps & { + disableRoutes?: boolean + customHeader?: React.ReactNode + } +> = (props) => { const { i18n } = useTranslation('general') const { refreshCookieAsync, user } = useAuth() - const { - id, - action, - apiURL, - collection, - customHeader, - data, - disableRoutes, - hasSavePermission, - internalState, - isEditing, - isLoading, - onSave: onSaveFromProps, - } = props + if ('collection' in 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 ( + +
+ +
+ + {!isLoading && ( + + + {disableRoutes ? ( + + ) : ( + + )} + + )} + +
+
+
+ ) + } + + return null } 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 9efc380ee..6a29c48a1 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,8 +1,6 @@ import React, { Fragment } from 'react' import { useTranslation } from 'react-i18next' -import type { Props } from '../types' - import { getTranslation } from '../../../../../../utilities/getTranslation' import { DocumentControls } from '../../../../elements/DocumentControls' import { Gutter } from '../../../../elements/Gutter' @@ -15,105 +13,117 @@ import Auth from '../Auth' import { SetStepNav } from '../SetStepNav' import Upload from '../Upload' import './index.scss' +import { EditViewProps } from '../../../types' const baseClass = 'collection-edit' -export const DefaultCollectionEdit: React.FC = (props) => { +export const DefaultCollectionEdit: React.FC< + EditViewProps & { + disableLeaveWithoutSaving?: boolean + disableActions?: boolean + } +> = (props) => { const { i18n, t } = useTranslation('general') - const { - id, - apiURL, - collection, - data, - disableActions, - disableLeaveWithoutSaving, - hasSavePermission, - internalState, - isEditing, - permissions, - } = props + if ('collection' in 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} - readOnly={!hasSavePermission} + return ( + + + +
+
+ - -
- {hasSidebar && ( -
-
-
-
- + {!(collection.versions?.drafts && collection.versions?.drafts?.autosave) && + !disableLeaveWithoutSaving && } + + {auth && ( + + )} + {upload && ( + + )} + !field?.admin?.position || field?.admin?.position !== 'sidebar'} + permissions={permissions.fields} + readOnly={!hasSavePermission} + /> + +
+ {hasSidebar && ( +
+
+
+
+ +
-
- )} -
- - ) + )} +
+ + ) + } + + return null } 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 f80a2e833..550c642a2 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,10 +1,9 @@ import React from 'react' -import type { Props } from '../types' - import VersionView from '../../../Version/Version' import VersionsView from '../../../Versions' import { DefaultCollectionEdit } from '../Default/index' +import { EditViewProps } from '../../../types' export type collectionViewType = | 'API' @@ -28,30 +27,36 @@ export const defaultCollectionViews: { } export const CustomCollectionComponent = ( - args: Props & { + args: EditViewProps & { view: collectionViewType }, ) => { - const { collection, view } = args + if ('collection' in 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 - const Component = - typeof Edit === 'object' && typeof Edit[view] === 'function' - ? Edit[view] - : typeof Edit === 'object' && - typeof Edit?.[view] === 'object' && - typeof Edit[view].Component === 'function' - ? 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 167e5f901..acdf3f287 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 @@ -5,64 +5,66 @@ import { Route, Switch, useRouteMatch } from 'react-router-dom' import { useAuth } from '../../../../utilities/Auth' import { useConfig } from '../../../../utilities/Config' import NotFound from '../../../NotFound' -import { type Props } from '../types' import { CustomCollectionComponent } from './CustomComponent' import { collectionCustomRoutes } from './custom' +import { EditViewProps } from '../../../types' // @ts-expect-error Just TypeScript being broken // TODO: Open TypeScript issue const Unauthorized = lazy(() => import('../../../Unauthorized')) -export const CollectionRoutes: React.FC = (props) => { - const { collection, permissions } = props +export const CollectionRoutes: React.FC = (props) => { + if ('collection' in 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?.readVersions?.permission ? ( - - ) : ( - - )} - - {collectionCustomRoutes({ - collection, - match, - permissions, - user, - })} - - - - - - - - ) + return ( + + + {permissions?.readVersions?.permission ? ( + + ) : ( + + )} + + + {permissions?.readVersions?.permission ? ( + + ) : ( + + )} + + {collectionCustomRoutes({ + collection, + match, + permissions, + user, + })} + + + + + + + + ) + } } 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 8afb0cbbe..16993d866 100644 --- a/packages/payload/src/admin/components/views/collections/Edit/index.tsx +++ b/packages/payload/src/admin/components/views/collections/Edit/index.tsx @@ -17,6 +17,7 @@ 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 @@ -124,27 +125,29 @@ const EditView: React.FC = (props) => { const isLoading = !internalState || !docPermissions || isLoadingData + const componentProps: EditViewProps = { + id, + action, + apiURL, + canAccessAdmin: permissions?.canAccessAdmin, + collection, + data, + hasSavePermission, + internalState, + isEditing, + isLoading, + onSave, + permissions: docPermissions as CollectionPermission, + updatedAt: updatedAt || data?.updatedAt, + user, + } + return ( ) diff --git a/packages/payload/src/admin/components/views/collections/Edit/types.ts b/packages/payload/src/admin/components/views/collections/Edit/types.ts index 3bda0a2ec..2eaf20a31 100644 --- a/packages/payload/src/admin/components/views/collections/Edit/types.ts +++ b/packages/payload/src/admin/components/views/collections/Edit/types.ts @@ -1,36 +1,6 @@ -import type React from 'react' - -import type { CollectionPermission } from '../../../../../auth/types' import type { SanitizedCollectionConfig } from '../../../../../collections/config/types' -import type { Document } from '../../../../../types' -import type { Fields } from '../../../forms/Form/types' export type IndexProps = { collection: SanitizedCollectionConfig isEditing?: boolean } - -export type Props = IndexProps & { - action: string - apiURL: string - autosaveEnabled: boolean - customHeader?: React.ReactNode - data: Document - disableActions?: boolean - disableLeaveWithoutSaving?: boolean - disableRoutes?: boolean - hasSavePermission: boolean - id?: string - internalState?: Fields - isLoading: boolean - onSave?: ( - json: Record & { - collectionConfig: SanitizedCollectionConfig - doc: Record - message: string - operation: 'create' | 'update' - }, - ) => void - permissions: CollectionPermission - updatedAt?: string -} diff --git a/packages/payload/src/admin/components/views/types.ts b/packages/payload/src/admin/components/views/types.ts new file mode 100644 index 000000000..38e99cd9a --- /dev/null +++ b/packages/payload/src/admin/components/views/types.ts @@ -0,0 +1,28 @@ +import { CollectionPermission, GlobalPermission, User } from '../../../auth' +import { Fields, SanitizedCollectionConfig, SanitizedGlobalConfig } from '../../../exports/types' + +export type EditViewProps = ( + | { + id: string + collection: SanitizedCollectionConfig + hasSavePermission: boolean + isEditing: boolean + internalState: Fields + initialState?: Fields + permissions: CollectionPermission + } + | { + global: SanitizedGlobalConfig + initialState: Fields + permissions: GlobalPermission + } +) & { + isLoading: boolean + onSave: (json: any) => void + updatedAt: string + data: any + user: User + canAccessAdmin: boolean + action: string + apiURL: string +} diff --git a/packages/payload/src/collections/config/types.ts b/packages/payload/src/collections/config/types.ts index 91525a568..739451b26 100644 --- a/packages/payload/src/collections/config/types.ts +++ b/packages/payload/src/collections/config/types.ts @@ -4,22 +4,27 @@ import type { GraphQLInputObjectType, GraphQLNonNull, GraphQLObjectType } from ' import type { Config as GeneratedTypes } from 'payload/generated-types' import type { DeepRequired } from 'ts-essentials' -import type { DocumentTab } from '../../admin/components/elements/DocumentHeader/Tabs/types' import type { CustomPreviewButtonProps, CustomPublishButtonProps, CustomSaveButtonProps, CustomSaveDraftButtonProps, } from '../../admin/components/elements/types' -import type { Props as EditProps } from '../../admin/components/views/collections/Edit/types' import type { Props as ListProps } from '../../admin/components/views/collections/List/types' import type { Auth, IncomingAuthType, User } from '../../auth/types' -import type { Access, Endpoint, EntityDescription, GeneratePreviewURL } from '../../config/types' +import type { + Access, + EditViewComponent, + Endpoint, + EntityDescription, + GeneratePreviewURL, +} from '../../config/types' import type { PayloadRequest, RequestContext } from '../../express/types' import type { Field } from '../../fields/config/types' import type { IncomingUploadType, Upload } from '../../uploads/types' import type { IncomingCollectionVersions, SanitizedCollectionVersions } from '../../versions/types' import type { AfterOperationArg, AfterOperationMap } from '../operations/utils' +import type { EditView } from '../../config/types' export type HookOperationType = | 'autosave' @@ -165,18 +170,6 @@ type BeforeDuplicateArgs = { export type BeforeDuplicate = (args: BeforeDuplicateArgs) => Promise | T -export type CollectionEditViewConfig = { - /** - * The component to render for this view - * + Replaces the default component - */ - Component: React.ComponentType - Tab: DocumentTab - path: string -} - -export type CollectionEditView = CollectionEditViewConfig | React.ComponentType - export type CollectionAdminOptions = { /** * Custom admin components @@ -213,32 +206,33 @@ export type CollectionAdminOptions = { } views?: { /** - * Replaces the "Edit" view entirely + * Set to a React component to replace the entire "Edit" view, including all nested routes. + * Set to an object to replace or modify individual nested routes, or to add new ones. */ Edit?: | { /** - * Replaces or adds nested views within the "Edit" view + * Replace or modify individual nested routes, or add new ones: * + `Default` - `/admin/collections/:collection/:id` * + `API` - `/admin/collections/:collection/:id/api` - * + `Preview` - `/admin/collections/:collection/:id/preview` + * + `LivePreview` - `/admin/collections/:collection/:id/preview` * + `References` - `/admin/collections/:collection/:id/references` * + `Relationships` - `/admin/collections/:collection/:id/relationships` * + `Versions` - `/admin/collections/:collection/:id/versions` * + `Version` - `/admin/collections/:collection/:id/versions/:version` * + `:path` - `/admin/collections/:collection/:id/:path` */ - Default: CollectionEditView - Versions?: CollectionEditView + Default?: EditView + Versions?: EditView + [key: string]: EditView // TODO: uncomment these as they are built - // [key: string]: CollectionEditView - // API?: CollectionEditView - // Preview?: CollectionEditView - // References?: CollectionEditView - // Relationships?: CollectionEditView - // Version: CollectionEditView + // API?: EditView + // LivePreview?: EditView + // References?: EditView + // Relationships?: EditView + // Version: EditView } - | React.ComponentType + | EditViewComponent List?: React.ComponentType } } diff --git a/packages/payload/src/config/types.ts b/packages/payload/src/config/types.ts index 1cac0ba41..e964f386b 100644 --- a/packages/payload/src/config/types.ts +++ b/packages/payload/src/config/types.ts @@ -22,6 +22,8 @@ import type { PayloadRequest } from '../express/types' import type { GlobalConfig, SanitizedGlobalConfig } from '../globals/config/types' import type { Payload } from '../payload' import type { Where } from '../types' +import { DocumentTab } from '../admin/components/elements/DocumentHeader/Tabs/types' +import { EditViewProps } from '../admin/components/views/types' type Prettify = { [K in keyof T]: T[K] @@ -201,6 +203,20 @@ export type CustomAdminView = React.ComponentType<{ user: User }> +export type EditViewConfig = { + /** + * The component to render for this view + * + Replaces the default component + */ + Component: EditViewComponent + Tab: DocumentTab + path: string +} + +export type EditViewComponent = React.ComponentType + +export type EditView = EditViewConfig | EditViewComponent + export type AdminRoute = { Component: CustomAdminView /** Whether the path should be matched exactly or as a prefix */ diff --git a/packages/payload/src/exports/components/views/Edit.ts b/packages/payload/src/exports/components/views/Edit.ts index aded21d57..df5d02bb6 100644 --- a/packages/payload/src/exports/components/views/Edit.ts +++ b/packages/payload/src/exports/components/views/Edit.ts @@ -1,2 +1 @@ export { default as Edit } from '../../../admin/components/views/collections/Edit/Default' -export type { Props } from '../../../admin/components/views/collections/Edit/types' diff --git a/packages/payload/src/globals/config/types.ts b/packages/payload/src/globals/config/types.ts index a85c3f51e..16f26bed2 100644 --- a/packages/payload/src/globals/config/types.ts +++ b/packages/payload/src/globals/config/types.ts @@ -1,8 +1,6 @@ import type { GraphQLNonNull, GraphQLObjectType } from 'graphql' -import type React from 'react' import type { DeepRequired } from 'ts-essentials' -import type { DocumentTab } from '../../admin/components/elements/DocumentHeader/Tabs/types' import type { CustomPreviewButtonProps, CustomPublishButtonProps, @@ -10,10 +8,18 @@ import type { CustomSaveDraftButtonProps, } from '../../admin/components/elements/types' import type { User } from '../../auth/types' -import type { Access, Endpoint, EntityDescription, GeneratePreviewURL } from '../../config/types' +import type { + Access, + EditView, + EditViewComponent, + Endpoint, + EntityDescription, + GeneratePreviewURL, +} from '../../config/types' import type { PayloadRequest } from '../../express/types' import type { Field } from '../../fields/config/types' import type { Where } from '../../types' + import type { IncomingGlobalVersions, SanitizedGlobalVersions } from '../../versions/types' export type TypeWithID = { @@ -39,18 +45,6 @@ export type AfterReadHook = (args: { req: PayloadRequest }) => any -export type GlobalEditViewConfig = { - /** - * The component to render for this view - * + Replaces the default component - */ - Component: React.ComponentType - Tab?: DocumentTab - path: string -} - -export type GlobalEditView = GlobalEditViewConfig | React.ComponentType - export type GlobalAdminOptions = { /** * Custom admin components @@ -80,32 +74,33 @@ export type GlobalAdminOptions = { } views?: { /** - * Replaces the "Edit" view + * Set to a React component to replace the entire "Edit" view, including all nested routes. + * Set to an object to replace or modify individual nested routes, or to add new ones. */ Edit?: | { /** - * Replaces or adds nested routes within the "Edit" view + * Replace or modify individual nested routes, or add new ones: * + `Default` - `/admin/globals/:slug` * + `API` - `/admin/globals/:id/api` - * + `Preview` - `/admin/globals/:id/preview` + * + `LivePreview` - `/admin/globals/:id/preview` * + `References` - `/admin/globals/:id/references` * + `Relationships` - `/admin/globals/:id/relationships` * + `Versions` - `/admin/globals/:id/versions` * + `Version` - `/admin/globals/:id/versions/:version` * + `:path` - `/admin/globals/:id/:path` */ - Default: GlobalEditView - Versions?: GlobalEditView + Default?: EditView + Versions?: EditView + [name: string]: EditView // TODO: uncomment these as they are built - // [name: string]: GlobalEditView - // API?: GlobalEditView - // Preview?: GlobalEditView - // References?: GlobalEditView - // Relationships?: GlobalEditView - // Version?: GlobalEditView + // API?: EditView + // LivePreview?: EditView + // References?: EditView + // Relationships?: EditView + // Version?: EditView } - | React.ComponentType + | EditViewComponent } } /** diff --git a/test/admin/components/views/CustomDefault/index.tsx b/test/admin/components/views/CustomDefault/index.tsx index e7743a5ed..05ac9af0b 100644 --- a/test/admin/components/views/CustomDefault/index.tsx +++ b/test/admin/components/views/CustomDefault/index.tsx @@ -3,9 +3,9 @@ import { Redirect } from 'react-router-dom' import { useStepNav } from '../../../../../packages/payload/src/admin/components/elements/StepNav' import { useConfig } from '../../../../../packages/payload/src/admin/components/utilities/Config' -import { type CustomAdminView } from '../../../../../packages/payload/src/config/types' +import { EditViewComponent } from '../../../../../packages/payload/src/config/types' -const CustomDefaultView: CustomAdminView = ({ +const CustomDefaultView: EditViewComponent = ({ canAccessAdmin, // collection, // global, diff --git a/test/admin/components/views/CustomEdit/index.tsx b/test/admin/components/views/CustomEdit/index.tsx index a03240a0a..0f793bc14 100644 --- a/test/admin/components/views/CustomEdit/index.tsx +++ b/test/admin/components/views/CustomEdit/index.tsx @@ -3,9 +3,9 @@ import { Redirect } from 'react-router-dom' import { useStepNav } from '../../../../../packages/payload/src/admin/components/elements/StepNav' import { useConfig } from '../../../../../packages/payload/src/admin/components/utilities/Config' -import { type CustomAdminView } from '../../../../../packages/payload/src/config/types' +import { EditViewComponent } from '../../../../../packages/payload/src/config/types' -const CustomEditView: CustomAdminView = ({ +const CustomEditView: EditViewComponent = ({ canAccessAdmin, // collection, // global, diff --git a/test/admin/components/views/CustomVersions/index.tsx b/test/admin/components/views/CustomVersions/index.tsx index 980b87426..24694d010 100644 --- a/test/admin/components/views/CustomVersions/index.tsx +++ b/test/admin/components/views/CustomVersions/index.tsx @@ -3,9 +3,9 @@ import { Redirect } from 'react-router-dom' import { useStepNav } from '../../../../../packages/payload/src/admin/components/elements/StepNav' import { useConfig } from '../../../../../packages/payload/src/admin/components/utilities/Config' -import { type CustomAdminView } from '../../../../../packages/payload/src/config/types' +import { EditViewComponent } from '../../../../../packages/payload/src/config/types' -const CustomVersionsView: CustomAdminView = ({ +const CustomVersionsView: EditViewComponent = ({ canAccessAdmin, // collection, // global, diff --git a/test/admin/components/views/CustomView/index.tsx b/test/admin/components/views/CustomView/index.tsx index cf003b9cf..a06fe4b16 100644 --- a/test/admin/components/views/CustomView/index.tsx +++ b/test/admin/components/views/CustomView/index.tsx @@ -1,9 +1,9 @@ import React, { Fragment, useEffect } from 'react' import { useStepNav } from '../../../../../packages/payload/src/admin/components/elements/StepNav' -import { type CustomAdminView } from '../../../../../packages/payload/src/config/types' +import { type EditViewComponent } from '../../../../../packages/payload/src/config/types' -const CustomView: CustomAdminView = () => { +const CustomView: EditViewComponent = () => { const { setStepNav } = useStepNav() // This effect will only run one time and will allow us diff --git a/test/admin/config.ts b/test/admin/config.ts index fc10bb7e1..8ba60e47b 100644 --- a/test/admin/config.ts +++ b/test/admin/config.ts @@ -64,6 +64,7 @@ export default buildConfigWithDefaults({ }, }, localization: { + defaultLocale: 'en', locales: ['en', 'es'], }, collections: [