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
This commit is contained in:
Paul
2024-03-26 11:48:00 -03:00
committed by GitHub
parent 436c4f2736
commit 9c7e7ed8d4
20 changed files with 327 additions and 399 deletions

View File

@@ -1,13 +1 @@
export type CustomPublishButtonProps = React.ComponentType<
DefaultPublishButtonProps & {
DefaultButton: React.ComponentType<DefaultPublishButtonProps>
}
>
export type DefaultPublishButtonProps = {
canPublish: boolean
disabled: boolean
id?: string
label: string
publish: () => void
}
export type CustomPublishButtonProps = React.ComponentType

View File

@@ -1,10 +1 @@
export type CustomSaveButtonProps = React.ComponentType<
DefaultSaveButtonProps & {
DefaultButton: React.ComponentType<DefaultSaveButtonProps>
}
>
export type DefaultSaveButtonProps = {
label: string
save: () => void
}
export type CustomSaveButtonProps = React.ComponentType

View File

@@ -1,11 +1 @@
export type CustomSaveDraftButtonProps = React.ComponentType<
DefaultSaveDraftButtonProps & {
DefaultButton: React.ComponentType<DefaultSaveDraftButtonProps>
}
>
export type DefaultSaveDraftButtonProps = {
disabled: boolean
label: string
saveDraft: () => void
}
export type CustomSaveDraftButtonProps = React.ComponentType

View File

@@ -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,

View File

@@ -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)) && (
<SaveDraft
CustomComponent={
collectionConfig?.admin?.components?.edit?.SaveDraftButton ||
globalConfig?.admin?.components?.elements?.SaveDraftButton
}
/>
<SaveDraft CustomComponent={componentMap.SaveDraftButton} />
)}
<Publish
CustomComponent={
collectionConfig?.admin?.components?.edit?.PublishButton ||
globalConfig?.admin?.components?.elements?.PublishButton
}
/>
<Publish CustomComponent={componentMap.PublishButton} />
</React.Fragment>
) : (
<Save
CustomComponent={
collectionConfig?.admin?.components?.edit?.SaveButton ||
globalConfig?.admin?.components?.elements?.SaveButton
}
/>
<Save CustomComponent={componentMap.SaveButton} />
)}
</React.Fragment>
)}

View File

@@ -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 (
<div>
{groups.map(({ entities, label }, key) => {
return (
<NavGroup key={key} label={label}>
{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 (
<LinkElement
// activeClassName="active"
className={`${baseClass}__link`}
href={href}
id={id}
key={i}
tabIndex={!navOpen ? -1 : undefined}
>
<span className={`${baseClass}__link-icon`}>
<Chevron direction="right" />
</span>
<span className={`${baseClass}__link-label`}>{entityLabel}</span>
</LinkElement>
)
})}
</NavGroup>
)
})}
</div>
)
}

View File

@@ -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 (
<NavWrapper baseClass={baseClass}>
<nav className={`${baseClass}__wrap`}>
{Array.isArray(beforeNavLinks) &&
beforeNavLinks.map((Component, i) => <Component key={i} />)}
{groups.map(({ entities, label }, key) => {
return (
<NavGroup key={key} label={label}>
{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 (
<LinkElement
// activeClassName="active"
className={`${baseClass}__link`}
href={href}
id={id}
// tabIndex={!navOpen ? -1 : undefined}
key={i}
>
<span className={`${baseClass}__link-icon`}>
<Chevron direction="right" />
</span>
<span className={`${baseClass}__link-label`}>{entityLabel}</span>
</LinkElement>
)
})}
</NavGroup>
)
})}
<DefaultNavClient />
{Array.isArray(afterNavLinks) && afterNavLinks.map((Component, i) => <Component key={i} />)}
<div className={`${baseClass}__controls`}>
<Logout />

View File

@@ -1,10 +1,8 @@
'use client'
import type { CustomPublishButtonProps, DefaultPublishButtonProps } from 'payload/types'
import qs from 'qs'
import React, { useCallback } from 'react'
import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js'
import { useForm, useFormModified } from '../../forms/Form/context.js'
import { FormSubmit } from '../../forms/Submit/index.js'
import { useConfig } from '../../providers/Config/index.js'
@@ -12,27 +10,7 @@ import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
import { useLocale } from '../../providers/Locale/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
const DefaultPublishButton: React.FC<DefaultPublishButtonProps> = ({
id,
canPublish,
disabled,
label,
publish,
}) => {
if (!canPublish) return null
return (
<FormSubmit buttonId={id} disabled={disabled} onClick={publish} size="small" type="button">
{label}
</FormSubmit>
)
}
type Props = {
CustomComponent?: CustomPublishButtonProps
}
export const Publish: React.FC<Props> = ({ CustomComponent }) => {
const DefaultPublishButton: React.FC = () => {
const { code } = useLocale()
const { id, collectionSlug, globalSlug, publishedDoc, unpublishedVersions } = useDocumentInfo()
const [hasPublishPermission, setHasPublishPermission] = React.useState(false)
@@ -43,9 +21,10 @@ export const Publish: React.FC<Props> = ({ CustomComponent }) => {
serverURL,
} = useConfig()
const { t } = useTranslation()
const label = t('version:publishChanges')
const hasNewerVersions = unpublishedVersions?.totalDocs > 0
const canPublish = modified || hasNewerVersions || !publishedDoc
const canPublish = hasPublishPermission && (modified || hasNewerVersions || !publishedDoc)
const publish = useCallback(() => {
void submit({
@@ -95,18 +74,27 @@ export const Publish: React.FC<Props> = ({ CustomComponent }) => {
void fetchPublishAccess()
}, [api, code, collectionSlug, getData, globalSlug, id, serverURL])
if (!canPublish) return null
return (
<RenderCustomComponent
CustomComponent={CustomComponent}
DefaultComponent={DefaultPublishButton}
componentProps={{
id: 'action-save',
DefaultButton: DefaultPublishButton,
canPublish: hasPublishPermission,
disabled: !canPublish,
label: t('version:publishChanges'),
publish,
}}
/>
<FormSubmit
buttonId={id.toString()}
disabled={!canPublish}
onClick={publish}
size="small"
type="button"
>
{label}
</FormSubmit>
)
}
type Props = {
CustomComponent?: React.ReactNode
}
export const Publish: React.FC<Props> = ({ CustomComponent }) => {
if (CustomComponent) return CustomComponent
return <DefaultPublishButton />
}

View File

@@ -1,16 +1,17 @@
'use client'
import type { CustomSaveButtonProps, DefaultSaveButtonProps } from 'payload/types'
import React, { useRef } from 'react'
import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js'
import { useForm } from '../../forms/Form/context.js'
import { FormSubmit } from '../../forms/Submit/index.js'
import { useHotkey } from '../../hooks/useHotkey.js'
import { useEditDepth } from '../../providers/EditDepth/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
const DefaultSaveButton: React.FC<DefaultSaveButtonProps> = ({ label, save }) => {
const DefaultSaveButton: React.FC = () => {
const { t } = useTranslation()
const { submit } = useForm()
const label = t('general:save')
const ref = useRef<HTMLButtonElement>(null)
const editDepth = useEditDepth()
@@ -23,29 +24,24 @@ const DefaultSaveButton: React.FC<DefaultSaveButtonProps> = ({ label, save }) =>
})
return (
<FormSubmit buttonId="action-save" onClick={save} ref={ref} size="small" type="button">
<FormSubmit
buttonId="action-save"
onClick={() => submit()}
ref={ref}
size="small"
type="button"
>
{label}
</FormSubmit>
)
}
type Props = {
CustomComponent?: CustomSaveButtonProps
CustomComponent?: React.ReactNode
}
export const Save: React.FC<Props> = ({ CustomComponent }) => {
const { t } = useTranslation()
const { submit } = useForm()
if (CustomComponent) return CustomComponent
return (
<RenderCustomComponent
CustomComponent={CustomComponent}
DefaultComponent={DefaultSaveButton}
componentProps={{
DefaultButton: DefaultSaveButton,
label: t('general:save'),
save: submit,
}}
/>
)
return <DefaultSaveButton />
}

View File

@@ -1,9 +1,7 @@
'use client'
import type { CustomSaveDraftButtonProps, DefaultSaveDraftButtonProps } from 'payload/types'
import React, { useCallback, useRef } from 'react'
import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js'
import { useForm, useFormModified } from '../../forms/Form/context.js'
import { FormSubmit } from '../../forms/Submit/index.js'
import { useHotkey } from '../../hooks/useHotkey.js'
@@ -15,57 +13,19 @@ import { useTranslation } from '../../providers/Translation/index.js'
const baseClass = 'save-draft'
const DefaultSaveDraftButton: React.FC<DefaultSaveDraftButtonProps> = ({
disabled,
label,
saveDraft,
}) => {
const ref = useRef<HTMLButtonElement>(null)
const editDepth = useEditDepth()
useHotkey({ cmdCtrlKey: true, editDepth, keyCodes: ['s'] }, (e) => {
if (disabled) {
return
}
e.preventDefault()
e.stopPropagation()
if (ref?.current) {
ref.current.click()
}
})
return (
<FormSubmit
buttonId="action-save-draft"
buttonStyle="secondary"
className={baseClass}
disabled={disabled}
onClick={saveDraft}
ref={ref}
size="small"
type="button"
>
{label}
</FormSubmit>
)
}
type Props = {
CustomComponent?: CustomSaveDraftButtonProps
}
export const SaveDraft: React.FC<Props> = ({ CustomComponent }) => {
const DefaultSaveDraftButton: React.FC = () => {
const {
routes: { api },
serverURL,
} = useConfig()
const { submit } = useForm()
const { id, collectionSlug, globalSlug } = useDocumentInfo()
const modified = useFormModified()
const { code: locale } = useLocale()
const ref = useRef<HTMLButtonElement>(null)
const editDepth = useEditDepth()
const { t } = useTranslation()
const canSaveDraft = modified
const { submit } = useForm()
const label = t('general:save')
const saveDraft = useCallback(async () => {
const search = `?locale=${locale}&depth=0&fallback-locale=null&draft=true`
@@ -91,16 +51,40 @@ export const SaveDraft: React.FC<Props> = ({ CustomComponent }) => {
})
}, [submit, collectionSlug, globalSlug, serverURL, api, locale, id])
useHotkey({ cmdCtrlKey: true, editDepth, keyCodes: ['s'] }, (e) => {
if (!modified) {
return
}
e.preventDefault()
e.stopPropagation()
if (ref?.current) {
ref.current.click()
}
})
return (
<RenderCustomComponent
CustomComponent={CustomComponent}
DefaultComponent={DefaultSaveDraftButton}
componentProps={{
DefaultButton: DefaultSaveDraftButton,
disabled: !canSaveDraft,
label: t('version:saveDraft'),
saveDraft,
}}
/>
<FormSubmit
buttonId="action-save-draft"
buttonStyle="secondary"
className={baseClass}
disabled={!modified}
onClick={saveDraft}
ref={ref}
size="small"
type="button"
>
{label}
</FormSubmit>
)
}
type Props = {
CustomComponent?: React.ReactNode
}
export const SaveDraft: React.FC<Props> = ({ CustomComponent }) => {
if (CustomComponent) return CustomComponent
return <DefaultSaveDraftButton />
}

View File

@@ -64,6 +64,18 @@ export const buildComponentMap = (args: {
const afterListTable = collectionConfig?.admin?.components?.AfterListTable
const SaveButton = collectionConfig?.admin?.components?.edit?.SaveButton
const SaveButtonComponent = SaveButton ? <SaveButton /> : undefined
const SaveDraftButton = collectionConfig?.admin?.components?.edit?.SaveDraftButton
const SaveDraftButtonComponent = SaveDraftButton ? <SaveDraftButton /> : undefined
/* const PreviewButton = collectionConfig?.admin?.components?.edit?.PreviewButton
const PreviewButtonComponent = PreviewButton ? <PreviewButton /> : undefined */
const PublishButton = collectionConfig?.admin?.components?.edit?.PublishButton
const PublishButtonComponent = PublishButton ? <PublishButton /> : undefined
const BeforeList =
(beforeList && Array.isArray(beforeList) && beforeList?.map((Component) => <Component />)) ||
null
@@ -99,6 +111,10 @@ export const buildComponentMap = (args: {
BeforeListTable,
Edit: <Edit collectionSlug={collectionConfig.slug} />,
List: <List collectionSlug={collectionConfig.slug} />,
/* PreviewButton: PreviewButtonComponent, */
PublishButton: PublishButtonComponent,
SaveButton: SaveButtonComponent,
SaveDraftButton: SaveDraftButtonComponent,
actionsMap: mapActions({
collectionConfig,
}),
@@ -121,6 +137,18 @@ export const buildComponentMap = (args: {
const editViewFromConfig = globalConfig?.admin?.components?.views?.Edit
const SaveButton = globalConfig?.admin?.components?.elements?.SaveButton
const SaveButtonComponent = SaveButton ? <SaveButton /> : undefined
const SaveDraftButton = globalConfig?.admin?.components?.elements?.SaveDraftButton
const SaveDraftButtonComponent = SaveDraftButton ? <SaveDraftButton /> : undefined
/* const PreviewButton = globalConfig?.admin?.components?.elements?.PreviewButton
const PreviewButtonComponent = PreviewButton ? <PreviewButton /> : undefined */
const PublishButton = globalConfig?.admin?.components?.elements?.PublishButton
const PublishButtonComponent = PublishButton ? <PublishButton /> : undefined
const CustomEditView =
typeof editViewFromConfig === 'function'
? editViewFromConfig
@@ -136,6 +164,10 @@ export const buildComponentMap = (args: {
const componentMap: GlobalComponentMap = {
Edit: <Edit globalSlug={globalConfig.slug} />,
/* PreviewButton: PreviewButtonComponent, */
PublishButton: PublishButtonComponent,
SaveButton: SaveButtonComponent,
SaveDraftButton: SaveDraftButtonComponent,
actionsMap: mapActions({
globalConfig,
}),

View File

@@ -103,6 +103,10 @@ export type GlobalComponentMap = ConfigComponentMapBase
export type ConfigComponentMapBase = {
Edit: React.ReactNode
/* PreviewButton: React.ReactNode */
PublishButton: React.ReactNode
SaveButton: React.ReactNode
SaveDraftButton: React.ReactNode
actionsMap: ActionMap
fieldMap: FieldMap
}

View File

@@ -0,0 +1,27 @@
'use client'
import { useForm } from '@payloadcms/ui/forms/Form'
import { useAuth } from '@payloadcms/ui/providers/Auth'
import { useTranslation } from '@payloadcms/ui/providers/Translation'
import React from 'react'
export const TestButton: React.FC = () => {
const { refreshPermissions } = useAuth()
const { submit } = useForm()
const { t } = useTranslation()
const label = t('general:save')
return (
<button
id="action-save"
onClick={(e) => {
e.preventDefault()
void refreshPermissions()
void submit()
}}
type="submit"
>
Custom: {label}
</button>
)
}

View File

@@ -2,6 +2,7 @@ import type { FieldAccess } from 'payload/types'
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
import { devUser } from '../credentials.js'
import { TestButton } from './TestButton.js'
import {
docLevelAccessSlug,
firstArrayText,
@@ -41,6 +42,35 @@ export default buildConfigWithDefaults({
admin: {
user: 'users',
},
globals: [
{
slug: 'settings',
fields: [
{
type: 'checkbox',
name: 'test',
label: 'Allow access to test global',
},
],
admin: {
components: {
elements: {
SaveButton: TestButton,
},
},
},
},
{
slug: 'test',
fields: [],
access: {
read: async ({ req: { payload } }) => {
const access = await payload.findGlobal({ slug: 'settings' })
return Boolean(access.test)
},
},
},
],
collections: [
{
slug: 'users',

View File

@@ -7,7 +7,13 @@ import { fileURLToPath } from 'url'
import type { ReadOnlyCollection, RestrictedVersion } from './payload-types.js'
import { exactText, initPageConsoleErrorCatch, openDocControls, openNav } from '../helpers.js'
import {
closeNav,
exactText,
initPageConsoleErrorCatch,
openDocControls,
openNav,
} from '../helpers.js'
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
import { initPayloadE2E } from '../helpers/initPayloadE2E.js'
import config from './config.js'
@@ -250,6 +256,30 @@ describe('access control', () => {
})
})
test('should show test global immediately after allowing access', async () => {
await page.goto(`${serverURL}/admin/globals/settings`)
await openNav(page)
// Ensure that we have loaded accesses by checking that settings collection
// at least is visible in the menu.
await expect(page.locator('#nav-global-settings')).toBeVisible()
// Test collection should be hidden at first.
await expect(page.locator('#nav-global-test')).toBeHidden()
await closeNav(page)
// Allow access to test global.
await page.locator('.checkbox-input:has(#field-test) input').check()
await page.locator('#action-save').click()
await openNav(page)
// Now test collection should appear in the menu.
await expect(page.locator('#nav-global-test')).toBeVisible()
})
test('maintain access control in document drawer', async () => {
const unrestrictedDoc = await payload.create({
collection: unrestrictedSlug,
@@ -260,7 +290,7 @@ describe('access control', () => {
// navigate to the `unrestricted` document and open the drawers to test access
const unrestrictedURL = new AdminUrlUtil(serverURL, unrestrictedSlug)
await page.goto(unrestrictedURL.edit(unrestrictedDoc.id))
await page.goto(unrestrictedURL.edit(unrestrictedDoc.id.toString()))
const addDocButton = page.locator(
'#userRestrictedDocs-add-new button.relationship-add-new__add-button.doc-drawer__toggler',
@@ -289,6 +319,7 @@ describe('access control', () => {
})
})
// eslint-disable-next-line @typescript-eslint/require-await
async function createDoc(data: any): Promise<TypeWithID & Record<string, unknown>> {
return payload.create({
collection: slug,

View File

@@ -1,8 +0,0 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
ignorePatterns: ['payload-types.ts'],
parserOptions: {
project: ['./tsconfig.eslint.json'],
tsconfigRootDir: __dirname,
},
}

View File

@@ -1,21 +0,0 @@
import type { EditViewProps } from 'payload/types'
import { EditView as DefaultEditView } from '@payloadcms/next/views'
import { useAuth } from '@payloadcms/ui/providers/Auth'
import React, { useCallback } from 'react'
const GlobalView: React.FC<EditViewProps> = (props) => {
const { onSave } = props
const { refreshPermissions } = useAuth()
const modifiedOnSave = useCallback(
(...args) => {
onSave.call(null, ...args)
void refreshPermissions()
},
[onSave, refreshPermissions],
)
return <DefaultEditView {...props} onSave={modifiedOnSave} />
}
export default GlobalView

View File

@@ -1,53 +0,0 @@
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
import { devUser } from '../credentials.js'
import GlobalViewWithRefresh from './GlobalViewWithRefresh.js'
export const pagesSlug = 'pages'
export default buildConfigWithDefaults({
globals: [
{
slug: 'settings',
fields: [
{
type: 'checkbox',
name: 'test',
label: 'Allow access to test global',
},
],
admin: {
components: {
views: {
Edit: GlobalViewWithRefresh,
},
},
},
},
{
slug: 'test',
fields: [],
access: {
read: async ({ req: { payload } }) => {
const access = await payload.findGlobal({ slug: 'settings' })
return access.test
},
},
},
],
collections: [
{
slug: 'users',
auth: true,
fields: [],
},
],
onInit: async (payload) => {
await payload.create({
collection: 'users',
data: {
email: devUser.email,
password: devUser.password,
},
})
},
})

View File

@@ -1,50 +0,0 @@
import type { Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import path from 'path'
import { fileURLToPath } from 'url'
import { closeNav, initPageConsoleErrorCatch, openNav } from '../helpers.js'
import { initPayloadE2E } from '../helpers/initPayloadE2E.js'
import config from './config.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
const { beforeAll, describe } = test
describe('refresh-permissions', () => {
let serverURL: string
let page: Page
beforeAll(async ({ browser }) => {
;({ serverURL } = await initPayloadE2E({ config, dirname }))
const context = await browser.newContext()
page = await context.newPage()
initPageConsoleErrorCatch(page)
})
test('should show test global immediately after allowing access', async () => {
await page.goto(`${serverURL}/admin/globals/settings`)
await openNav(page)
// Ensure that we have loaded accesses by checking that settings collection
// at least is visible in the menu.
await expect(page.locator('#nav-global-settings')).toBeVisible()
// Test collection should be hidden at first.
await expect(page.locator('#nav-global-test')).toBeHidden()
await closeNav(page)
// Allow access to test global.
await page.locator('.checkbox-input:has(#field-test) input').check()
await page.locator('#action-save').click()
await openNav(page)
// Now test collection should appear in the menu.
await expect(page.locator('#nav-global-test')).toBeVisible()
})
})

View File

@@ -1,13 +0,0 @@
{
// extend your base config to share compilerOptions, etc
//"extends": "./tsconfig.json",
"compilerOptions": {
// ensure that nobody can accidentally use this config for a build
"noEmit": true
},
"include": [
// whatever paths you intend to lint
"./**/*.ts",
"./**/*.tsx"
]
}