From 9c7e7ed8d44a4faf907d47c2bdd1adefb77ad20f Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 26 Mar 2024 11:48:00 -0300 Subject: [PATCH] fix(ui): custom buttons and e2e refresh permissions test (#5458) * moved refresh permissions test suite to access control * support for custom Save, SaveDraft and Publish buttons in admin config for collections and globals * moved navigation content to client side so that permissions can be refreshed from active state --- .../src/admin/elements/PublishButton.ts | 14 +-- .../payload/src/admin/elements/SaveButton.ts | 11 +- .../src/admin/elements/SaveDraftButton.ts | 12 +- packages/payload/src/admin/types.ts | 3 - .../src/elements/DocumentControls/index.tsx | 28 ++--- packages/ui/src/elements/Nav/index.client.tsx | 107 ++++++++++++++++++ packages/ui/src/elements/Nav/index.tsx | 92 +-------------- packages/ui/src/elements/Publish/index.tsx | 60 ++++------ packages/ui/src/elements/Save/index.tsx | 32 +++--- packages/ui/src/elements/SaveDraft/index.tsx | 94 +++++++-------- .../ComponentMap/buildComponentMap/index.tsx | 32 ++++++ .../ComponentMap/buildComponentMap/types.ts | 4 + test/access-control/TestButton.tsx | 27 +++++ test/access-control/config.ts | 30 +++++ test/access-control/e2e.spec.ts | 35 +++++- test/refresh-permissions/.eslintrc.cjs | 8 -- .../GlobalViewWithRefresh.tsx | 21 ---- test/refresh-permissions/config.ts | 53 --------- test/refresh-permissions/e2e.spec.ts | 50 -------- test/refresh-permissions/tsconfig.eslint.json | 13 --- 20 files changed, 327 insertions(+), 399 deletions(-) create mode 100644 packages/ui/src/elements/Nav/index.client.tsx create mode 100644 test/access-control/TestButton.tsx delete mode 100644 test/refresh-permissions/.eslintrc.cjs delete mode 100644 test/refresh-permissions/GlobalViewWithRefresh.tsx delete mode 100644 test/refresh-permissions/config.ts delete mode 100644 test/refresh-permissions/e2e.spec.ts delete mode 100644 test/refresh-permissions/tsconfig.eslint.json diff --git a/packages/payload/src/admin/elements/PublishButton.ts b/packages/payload/src/admin/elements/PublishButton.ts index b4ab0a434..ae18e2bb3 100644 --- a/packages/payload/src/admin/elements/PublishButton.ts +++ b/packages/payload/src/admin/elements/PublishButton.ts @@ -1,13 +1 @@ -export type CustomPublishButtonProps = React.ComponentType< - DefaultPublishButtonProps & { - DefaultButton: React.ComponentType - } -> - -export type DefaultPublishButtonProps = { - canPublish: boolean - disabled: boolean - id?: string - label: string - publish: () => void -} +export type CustomPublishButtonProps = React.ComponentType diff --git a/packages/payload/src/admin/elements/SaveButton.ts b/packages/payload/src/admin/elements/SaveButton.ts index d076d9e59..db78e8464 100644 --- a/packages/payload/src/admin/elements/SaveButton.ts +++ b/packages/payload/src/admin/elements/SaveButton.ts @@ -1,10 +1 @@ -export type CustomSaveButtonProps = React.ComponentType< - DefaultSaveButtonProps & { - DefaultButton: React.ComponentType - } -> - -export type DefaultSaveButtonProps = { - label: string - save: () => void -} +export type CustomSaveButtonProps = React.ComponentType diff --git a/packages/payload/src/admin/elements/SaveDraftButton.ts b/packages/payload/src/admin/elements/SaveDraftButton.ts index 517c92732..52d1662d6 100644 --- a/packages/payload/src/admin/elements/SaveDraftButton.ts +++ b/packages/payload/src/admin/elements/SaveDraftButton.ts @@ -1,11 +1 @@ -export type CustomSaveDraftButtonProps = React.ComponentType< - DefaultSaveDraftButtonProps & { - DefaultButton: React.ComponentType - } -> - -export type DefaultSaveDraftButtonProps = { - disabled: boolean - label: string - saveDraft: () => void -} +export type CustomSaveDraftButtonProps = React.ComponentType diff --git a/packages/payload/src/admin/types.ts b/packages/payload/src/admin/types.ts index e74c9c33d..20bfd82a0 100644 --- a/packages/payload/src/admin/types.ts +++ b/packages/payload/src/admin/types.ts @@ -4,11 +4,8 @@ export type { ConditionalDateProps } from './elements/DatePicker.js' export type { DayPickerProps, SharedProps, TimePickerProps } from './elements/DatePicker.js' export type { DefaultPreviewButtonProps } from './elements/PreviewButton.js' export type { CustomPreviewButtonProps } from './elements/PreviewButton.js' -export type { DefaultPublishButtonProps } from './elements/PublishButton.js' export type { CustomPublishButtonProps } from './elements/PublishButton.js' -export type { DefaultSaveButtonProps } from './elements/SaveButton.js' export type { CustomSaveButtonProps } from './elements/SaveButton.js' -export type { DefaultSaveDraftButtonProps } from './elements/SaveDraftButton.js' export type { CustomSaveDraftButtonProps } from './elements/SaveDraftButton.js' export type { DocumentTab, diff --git a/packages/ui/src/elements/DocumentControls/index.tsx b/packages/ui/src/elements/DocumentControls/index.tsx index ec9c61385..f8b57898e 100644 --- a/packages/ui/src/elements/DocumentControls/index.tsx +++ b/packages/ui/src/elements/DocumentControls/index.tsx @@ -2,6 +2,7 @@ import type { CollectionPermission, GlobalPermission } from 'payload/auth' import type { SanitizedCollectionConfig } from 'payload/types' +import { useComponentMap } from '@payloadcms/ui/providers/ComponentMap' import React, { Fragment } from 'react' import { useConfig } from '../../providers/Config/index.js' @@ -47,10 +48,16 @@ export const DocumentControls: React.FC<{ const { i18n } = useTranslation() const config = useConfig() + const { getComponentMap } = useComponentMap() const collectionConfig = config.collections.find((coll) => coll.slug === slug) const globalConfig = config.globals.find((global) => global.slug === slug) + const componentMap = getComponentMap({ + collectionSlug: collectionConfig?.slug, + globalSlug: globalConfig?.slug, + }) + const { admin: { dateFormat }, routes: { admin: adminRoute }, @@ -164,27 +171,12 @@ export const DocumentControls: React.FC<{ !collectionConfig?.versions?.drafts?.autosave) || (globalConfig?.versions?.drafts && !globalConfig?.versions?.drafts?.autosave)) && ( - + )} - + ) : ( - + )} )} diff --git a/packages/ui/src/elements/Nav/index.client.tsx b/packages/ui/src/elements/Nav/index.client.tsx new file mode 100644 index 000000000..d5b54a595 --- /dev/null +++ b/packages/ui/src/elements/Nav/index.client.tsx @@ -0,0 +1,107 @@ +'use client' +import { getTranslation } from '@payloadcms/translations' +import LinkWithDefault from 'next/link.js' +import { isEntityHidden } from 'payload/utilities' +import React from 'react' + +import type { EntityToGroup } from '../../utilities/groupNavItems.js' + +import { Chevron } from '../../icons/Chevron/index.js' +import { useAuth } from '../../providers/Auth/index.js' +import { useConfig } from '../../providers/Config/index.js' +import { useTranslation } from '../../providers/Translation/index.js' +import { EntityType, groupNavItems } from '../../utilities/groupNavItems.js' +import { NavGroup } from '../NavGroup/index.js' +import { useNav } from './context.js' + +const baseClass = 'nav' + +export const DefaultNavClient: React.FC = () => { + const { permissions, user } = useAuth() + const { + collections, + globals, + routes: { admin }, + } = useConfig() + + const { i18n } = useTranslation() + const { navOpen } = useNav() + + const groups = groupNavItems( + [ + ...collections + // @ts-expect-error todo: fix type error here + .filter(({ admin: { hidden } }) => !isEntityHidden({ hidden, user })) + .map((collection) => { + const entityToGroup: EntityToGroup = { + type: EntityType.collection, + entity: collection, + } + + return entityToGroup + }), + ...globals + // @ts-expect-error todo: fix type error here + .filter(({ admin: { hidden } }) => !isEntityHidden({ hidden, user })) + .map((global) => { + const entityToGroup: EntityToGroup = { + type: EntityType.global, + entity: global, + } + + return entityToGroup + }), + ], + permissions, + i18n, + ) + + return ( +
+ {groups.map(({ entities, label }, key) => { + return ( + + {entities.map(({ type, entity }, i) => { + let entityLabel: string + let href: string + let id: string + + if (type === EntityType.collection) { + href = `${admin}/collections/${entity.slug}` + entityLabel = getTranslation(entity.labels.plural, i18n) + id = `nav-${entity.slug}` + } + + if (type === EntityType.global) { + href = `${admin}/globals/${entity.slug}` + entityLabel = getTranslation(entity.label, i18n) + id = `nav-global-${entity.slug}` + } + + const Link = (LinkWithDefault.default || + LinkWithDefault) as typeof LinkWithDefault.default + + const LinkElement = Link || 'a' + + return ( + + + + + {entityLabel} + + ) + })} + + ) + })} +
+ ) +} diff --git a/packages/ui/src/elements/Nav/index.tsx b/packages/ui/src/elements/Nav/index.tsx index 87fd4f727..7d4448a28 100644 --- a/packages/ui/src/elements/Nav/index.tsx +++ b/packages/ui/src/elements/Nav/index.tsx @@ -1,31 +1,20 @@ -import type { I18n } from '@payloadcms/translations' -import type { Permissions, User } from 'payload/auth' import type { SanitizedConfig } from 'payload/types' -import { getTranslation } from '@payloadcms/translations' -import LinkWithDefault from 'next/link.js' -import { isEntityHidden } from 'payload/utilities' import React from 'react' -import type { EntityToGroup } from '../../utilities/groupNavItems.js' - -import { Chevron } from '../../icons/Chevron/index.js' -import { EntityType, groupNavItems } from '../../utilities/groupNavItems.js' import { Logout } from '../Logout/index.js' -import { NavGroup } from '../NavGroup/index.js' import { NavHamburger } from './NavHamburger/index.js' import { NavWrapper } from './NavWrapper/index.js' import './index.scss' const baseClass = 'nav' +import { DefaultNavClient } from './index.client.js' + export const DefaultNav: React.FC<{ config: SanitizedConfig - i18n: I18n - permissions: Permissions - user: User }> = (props) => { - const { config, i18n, permissions, user } = props + const { config } = props if (!config) { return null @@ -35,87 +24,14 @@ export const DefaultNav: React.FC<{ admin: { components: { afterNavLinks, beforeNavLinks }, }, - collections, - globals, - routes: { admin }, } = config - const groups = groupNavItems( - [ - ...collections - .filter(({ admin: { hidden } }) => !isEntityHidden({ hidden, user })) - .map((collection) => { - const entityToGroup: EntityToGroup = { - type: EntityType.collection, - entity: collection, - } - - return entityToGroup - }), - ...globals - .filter(({ admin: { hidden } }) => !isEntityHidden({ hidden, user })) - .map((global) => { - const entityToGroup: EntityToGroup = { - type: EntityType.global, - entity: global, - } - - return entityToGroup - }), - ], - permissions, - i18n, - ) - return (