chore!: adjusts auth hydration from server (#7545)
Fixes https://github.com/payloadcms/payload/issues/6823 Allows the server to initialize the AuthProvider via props. Renames `HydrateClientUser` to `HydrateAuthProvider`. It now only hydrates the permissions as the user can be set from props. Permissions can be initialized from props, but still need to be hydrated for some pages as access control can be specific to docs/lists etc. **BREAKING CHANGE** - Renames exported `HydrateClientUser` to `HydrateAuthProvider`
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
} from '@payloadcms/ui'
|
||||
import { EntityType, formatAdminURL, groupNavItems } from '@payloadcms/ui/shared'
|
||||
import LinkWithDefault from 'next/link.js'
|
||||
import { usePathname } from 'next/navigation.js'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
const baseClass = 'nav'
|
||||
@@ -21,6 +22,7 @@ const baseClass = 'nav'
|
||||
export const DefaultNavClient: React.FC = () => {
|
||||
const { permissions } = useAuth()
|
||||
const { isEntityVisible } = useEntityVisibility()
|
||||
const pathname = usePathname()
|
||||
|
||||
const {
|
||||
collections,
|
||||
@@ -84,17 +86,11 @@ export const DefaultNavClient: React.FC = () => {
|
||||
LinkWithDefault) as typeof LinkWithDefault.default
|
||||
|
||||
const LinkElement = Link || 'a'
|
||||
|
||||
const activeCollection = window?.location?.pathname
|
||||
?.split('/')
|
||||
.find(
|
||||
(_, index, arr) =>
|
||||
arr[index - 1] === 'collections' || arr[index - 1] === 'globals',
|
||||
)
|
||||
const activeCollection = pathname.startsWith(href)
|
||||
|
||||
return (
|
||||
<LinkElement
|
||||
className={[`${baseClass}__link`, activeCollection === entity?.slug && `active`]
|
||||
className={[`${baseClass}__link`, activeCollection && `active`]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
href={href}
|
||||
@@ -102,9 +98,11 @@ export const DefaultNavClient: React.FC = () => {
|
||||
key={i}
|
||||
tabIndex={!navOpen ? -1 : undefined}
|
||||
>
|
||||
<span className={`${baseClass}__link-icon`}>
|
||||
<ChevronIcon direction="right" />
|
||||
</span>
|
||||
{activeCollection && (
|
||||
<span className={`${baseClass}__link-icon`}>
|
||||
<ChevronIcon direction="right" />
|
||||
</span>
|
||||
)}
|
||||
<span className={`${baseClass}__link-label`}>{entityLabel}</span>
|
||||
</LinkElement>
|
||||
)
|
||||
|
||||
@@ -110,16 +110,9 @@
|
||||
&__link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.active {
|
||||
.nav__link-icon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__link-icon {
|
||||
display: none;
|
||||
margin-right: calc(var(--base) * 0.25);
|
||||
top: -1px;
|
||||
position: relative;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { AcceptedLanguages, I18nClient } from '@payloadcms/translations'
|
||||
import type { SanitizedConfig } from 'payload'
|
||||
import type { PayloadRequest, SanitizedConfig } from 'payload'
|
||||
|
||||
import { initI18n, rtlLanguages } from '@payloadcms/translations'
|
||||
import { RootProvider } from '@payloadcms/ui'
|
||||
import '@payloadcms/ui/scss/app.scss'
|
||||
import { buildComponentMap } from '@payloadcms/ui/utilities/buildComponentMap'
|
||||
import { headers as getHeaders, cookies as nextCookies } from 'next/headers.js'
|
||||
import { createClientConfig, parseCookies } from 'payload'
|
||||
import { createClientConfig, createLocalReq, parseCookies } from 'payload'
|
||||
import * as qs from 'qs-esm'
|
||||
import React from 'react'
|
||||
|
||||
import { getPayloadHMR } from '../../utilities/getPayloadHMR.js'
|
||||
@@ -52,6 +53,20 @@ export const RootLayout = async ({
|
||||
language: languageCode,
|
||||
})
|
||||
|
||||
const req = await createLocalReq(
|
||||
{
|
||||
fallbackLocale: null,
|
||||
req: {
|
||||
headers,
|
||||
host: headers.get('host'),
|
||||
i18n,
|
||||
url: `${payload.config.serverURL}`,
|
||||
} as PayloadRequest,
|
||||
},
|
||||
payload,
|
||||
)
|
||||
const { permissions, user } = await payload.auth({ headers, req })
|
||||
|
||||
const clientConfig = await createClientConfig({ config, t: i18n.t })
|
||||
|
||||
const dir = (rtlLanguages as unknown as AcceptedLanguages[]).includes(languageCode)
|
||||
@@ -100,9 +115,11 @@ export const RootLayout = async ({
|
||||
fallbackLang={clientConfig.i18n.fallbackLanguage}
|
||||
languageCode={languageCode}
|
||||
languageOptions={languageOptions}
|
||||
permissions={permissions}
|
||||
switchLanguageServerAction={switchLanguageServerAction}
|
||||
theme={theme}
|
||||
translations={i18n.translations}
|
||||
user={user}
|
||||
>
|
||||
{wrappedChildren}
|
||||
</RootProvider>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { AdminViewProps, ServerSideEditViewProps } from 'payload'
|
||||
|
||||
import { DocumentInfoProvider, HydrateClientUser } from '@payloadcms/ui'
|
||||
import { DocumentInfoProvider, HydrateAuthProvider } from '@payloadcms/ui'
|
||||
import { RenderCustomComponent } from '@payloadcms/ui/shared'
|
||||
import { notFound } from 'next/navigation.js'
|
||||
import React from 'react'
|
||||
@@ -82,7 +82,7 @@ export const Account: React.FC<AdminViewProps> = async ({
|
||||
i18n={i18n}
|
||||
permissions={permissions}
|
||||
/>
|
||||
<HydrateClientUser permissions={permissions} user={user} />
|
||||
<HydrateAuthProvider permissions={permissions} />
|
||||
<RenderCustomComponent
|
||||
CustomComponent={
|
||||
typeof CustomAccountComponent === 'function' ? CustomAccountComponent : undefined
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { EntityToGroup } from '@payloadcms/ui/shared'
|
||||
import type { AdminViewProps } from 'payload'
|
||||
|
||||
import { HydrateClientUser } from '@payloadcms/ui'
|
||||
import { HydrateAuthProvider } from '@payloadcms/ui'
|
||||
import { EntityType, RenderCustomComponent, groupNavItems } from '@payloadcms/ui/shared'
|
||||
import LinkImport from 'next/link.js'
|
||||
import React, { Fragment } from 'react'
|
||||
@@ -79,7 +79,6 @@ export const Dashboard: React.FC<AdminViewProps> = ({ initPageResult, params, se
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<HydrateClientUser permissions={permissions} user={user} />
|
||||
<RenderCustomComponent
|
||||
CustomComponent={
|
||||
typeof CustomDashboardComponent === 'function' ? CustomDashboardComponent : undefined
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import type { AdminViewComponent, AdminViewProps, EditViewComponent } from 'payload'
|
||||
|
||||
import { DocumentInfoProvider, EditDepthProvider, HydrateClientUser } from '@payloadcms/ui'
|
||||
import { DocumentInfoProvider, EditDepthProvider, HydrateAuthProvider } from '@payloadcms/ui'
|
||||
import {
|
||||
RenderCustomComponent,
|
||||
formatAdminURL,
|
||||
isEditing as getIsEditing,
|
||||
} from '@payloadcms/ui/shared'
|
||||
import { notFound, redirect } from 'next/navigation.js'
|
||||
import { deepCopyObjectSimple } from 'payload'
|
||||
import React from 'react'
|
||||
|
||||
import type { GenerateEditViewMetadata } from './getMetaBySegment.js'
|
||||
@@ -213,6 +212,7 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
permissions={permissions}
|
||||
/>
|
||||
)}
|
||||
<HydrateAuthProvider permissions={permissions} />
|
||||
{/**
|
||||
* After bumping the Next.js canary to 104, and React to 19.0.0-rc-06d0b89e-20240801" we have to deepCopy the permissions object (https://github.com/payloadcms/payload/pull/7541).
|
||||
* If both HydrateClientUser and RenderCustomComponent receive the same permissions object (same object reference), we get a
|
||||
@@ -221,7 +221,6 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
*
|
||||
* // TODO: Revisit this in the future and figure out why this is happening. Might be a React/Next.js bug. We don't know why it happens, and a future React/Next version might unbreak this (keep an eye on this and remove deepCopyObjectSimple if that's the case)
|
||||
*/}
|
||||
<HydrateClientUser permissions={deepCopyObjectSimple(permissions)} user={user} />
|
||||
<EditDepthProvider
|
||||
depth={1}
|
||||
key={`${collectionSlug || globalSlug}${locale?.code ? `-${locale?.code}` : ''}`}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { AdminViewProps, Where } from 'payload'
|
||||
|
||||
import {
|
||||
HydrateClientUser,
|
||||
HydrateAuthProvider,
|
||||
ListInfoProvider,
|
||||
ListQueryProvider,
|
||||
TableColumnsProvider,
|
||||
@@ -138,7 +138,7 @@ export const ListView: React.FC<AdminViewProps> = async ({
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<HydrateClientUser permissions={permissions} user={user} />
|
||||
<HydrateAuthProvider permissions={permissions} />
|
||||
<ListInfoProvider
|
||||
collectionConfig={createClientCollectionConfig({
|
||||
collection: collectionConfig,
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { I18n } from '@payloadcms/translations'
|
||||
import type { Metadata } from 'next'
|
||||
import type { AdminViewComponent, SanitizedConfig } from 'payload'
|
||||
|
||||
import { HydrateClientUser } from '@payloadcms/ui'
|
||||
import { HydrateAuthProvider } from '@payloadcms/ui'
|
||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
@@ -58,21 +58,18 @@ export const NotFoundPage = async ({
|
||||
})
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<HydrateClientUser permissions={initPageResult.permissions} user={initPageResult.req.user} />
|
||||
<DefaultTemplate
|
||||
i18n={initPageResult.req.i18n}
|
||||
locale={initPageResult.locale}
|
||||
params={params}
|
||||
payload={initPageResult.req.payload}
|
||||
permissions={initPageResult.permissions}
|
||||
searchParams={searchParams}
|
||||
user={initPageResult.req.user}
|
||||
visibleEntities={initPageResult.visibleEntities}
|
||||
>
|
||||
<NotFoundClient />
|
||||
</DefaultTemplate>
|
||||
</Fragment>
|
||||
<DefaultTemplate
|
||||
i18n={initPageResult.req.i18n}
|
||||
locale={initPageResult.locale}
|
||||
params={params}
|
||||
payload={initPageResult.req.payload}
|
||||
permissions={initPageResult.permissions}
|
||||
searchParams={searchParams}
|
||||
user={initPageResult.req.user}
|
||||
visibleEntities={initPageResult.visibleEntities}
|
||||
>
|
||||
<NotFoundClient />
|
||||
</DefaultTemplate>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ const generateLabelFromValue = (
|
||||
}
|
||||
} else if (relatedDoc) {
|
||||
// Handle non-polymorphic `hasMany` relationships or fallback
|
||||
if (typeof relatedDoc.id !== 'undefined') {
|
||||
if (typeof relatedDoc?.id !== 'undefined') {
|
||||
valueToReturn = relatedDoc.id
|
||||
} else {
|
||||
valueToReturn = relatedDoc
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type {
|
||||
CollectionPermission,
|
||||
Document,
|
||||
EditViewComponent,
|
||||
GlobalPermission,
|
||||
OptionObject,
|
||||
} from 'payload'
|
||||
|
||||
import { notFound } from 'next/navigation.js'
|
||||
import {
|
||||
type CollectionPermission,
|
||||
type Document,
|
||||
type EditViewComponent,
|
||||
type GlobalPermission,
|
||||
type OptionObject,
|
||||
deepCopyObjectSimple,
|
||||
} from 'payload'
|
||||
import React from 'react'
|
||||
|
||||
import { getLatestVersion } from '../Versions/getLatestVersion.js'
|
||||
@@ -55,8 +55,18 @@ export const VersionView: EditViewComponent = async (props) => {
|
||||
})
|
||||
|
||||
if (collectionConfig?.versions?.drafts) {
|
||||
latestDraftVersion = await getLatestVersion(payload, slug, 'draft', 'collection')
|
||||
latestPublishedVersion = await getLatestVersion(payload, slug, 'published', 'collection')
|
||||
latestDraftVersion = await getLatestVersion({
|
||||
slug,
|
||||
type: 'collection',
|
||||
payload,
|
||||
status: 'draft',
|
||||
})
|
||||
latestPublishedVersion = await getLatestVersion({
|
||||
slug,
|
||||
type: 'collection',
|
||||
payload,
|
||||
status: 'published',
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
return notFound()
|
||||
@@ -80,8 +90,18 @@ export const VersionView: EditViewComponent = async (props) => {
|
||||
})
|
||||
|
||||
if (globalConfig?.versions?.drafts) {
|
||||
latestDraftVersion = await getLatestVersion(payload, slug, 'draft', 'global')
|
||||
latestPublishedVersion = await getLatestVersion(payload, slug, 'published', 'global')
|
||||
latestDraftVersion = await getLatestVersion({
|
||||
slug,
|
||||
type: 'global',
|
||||
payload,
|
||||
status: 'draft',
|
||||
})
|
||||
latestPublishedVersion = await getLatestVersion({
|
||||
slug,
|
||||
type: 'global',
|
||||
payload,
|
||||
status: 'published',
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
return notFound()
|
||||
@@ -116,7 +136,14 @@ export const VersionView: EditViewComponent = async (props) => {
|
||||
return (
|
||||
<DefaultVersionView
|
||||
doc={doc}
|
||||
docPermissions={docPermissions}
|
||||
/**
|
||||
* After bumping the Next.js canary to 104, and React to 19.0.0-rc-06d0b89e-20240801" we have to deepCopy the permissions object (https://github.com/payloadcms/payload/pull/7541).
|
||||
* If both HydrateClientUser and RenderCustomComponent receive the same permissions object (same object reference), we get a
|
||||
* "TypeError: Cannot read properties of undefined (reading '$$typeof')" error
|
||||
*
|
||||
* // TODO: Revisit this in the future and figure out why this is happening. Might be a React/Next.js bug. We don't know why it happens, and a future React/Next version might unbreak this (keep an eye on this and remove deepCopyObjectSimple if that's the case)
|
||||
*/
|
||||
docPermissions={deepCopyObjectSimple(docPermissions)}
|
||||
initialComparisonDoc={latestVersion}
|
||||
latestDraftVersion={latestDraftVersion?.id}
|
||||
latestPublishedVersion={latestPublishedVersion?.id}
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
export async function getLatestVersion(payload, slug, status, type = 'collection') {
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
type ReturnType = {
|
||||
id: string
|
||||
updatedAt: string
|
||||
} | null
|
||||
|
||||
type Args = {
|
||||
payload: Payload
|
||||
slug: string
|
||||
status: 'draft' | 'published'
|
||||
type: 'collection' | 'global'
|
||||
}
|
||||
export async function getLatestVersion(args: Args): Promise<ReturnType> {
|
||||
const { slug, type = 'collection', payload, status } = args
|
||||
|
||||
try {
|
||||
const sharedOptions = {
|
||||
depth: 0,
|
||||
@@ -22,11 +37,16 @@ export async function getLatestVersion(payload, slug, status, type = 'collection
|
||||
...sharedOptions,
|
||||
})
|
||||
|
||||
if (!response.docs.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
id: response.docs[0].id,
|
||||
updatedAt: response.docs[0].updatedAt,
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,13 +62,18 @@ export const VersionsView: EditViewComponent = async (props) => {
|
||||
},
|
||||
})
|
||||
if (collectionConfig?.versions?.drafts) {
|
||||
latestDraftVersion = await getLatestVersion(payload, collectionSlug, 'draft', 'collection')
|
||||
latestPublishedVersion = await getLatestVersion(
|
||||
latestDraftVersion = await getLatestVersion({
|
||||
slug: collectionSlug,
|
||||
type: 'collection',
|
||||
payload,
|
||||
collectionSlug,
|
||||
'published',
|
||||
'collection',
|
||||
)
|
||||
status: 'draft',
|
||||
})
|
||||
latestPublishedVersion = await getLatestVersion({
|
||||
slug: collectionSlug,
|
||||
type: 'collection',
|
||||
payload,
|
||||
status: 'published',
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error) // eslint-disable-line no-console
|
||||
@@ -90,8 +95,18 @@ export const VersionsView: EditViewComponent = async (props) => {
|
||||
})
|
||||
|
||||
if (globalConfig?.versions?.drafts) {
|
||||
latestDraftVersion = await getLatestVersion(payload, globalSlug, 'draft', 'global')
|
||||
latestPublishedVersion = await getLatestVersion(payload, globalSlug, 'published', 'global')
|
||||
latestDraftVersion = await getLatestVersion({
|
||||
slug: globalSlug,
|
||||
type: 'global',
|
||||
payload,
|
||||
status: 'draft',
|
||||
})
|
||||
latestPublishedVersion = await getLatestVersion({
|
||||
slug: globalSlug,
|
||||
type: 'global',
|
||||
payload,
|
||||
status: 'published',
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error) // eslint-disable-line no-console
|
||||
|
||||
27
packages/ui/src/elements/HydrateAuthProvider/index.tsx
Normal file
27
packages/ui/src/elements/HydrateAuthProvider/index.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
'use client'
|
||||
|
||||
import type { Permissions } from 'payload'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { useAuth } from '../../providers/Auth/index.js'
|
||||
|
||||
/**
|
||||
* The Auth Provider wraps the entire app
|
||||
* but each page has specific permissions
|
||||
*
|
||||
* i.e. access control on documents/fields on a document
|
||||
*/
|
||||
|
||||
type Props = {
|
||||
permissions: Permissions
|
||||
}
|
||||
export function HydrateAuthProvider({ permissions }: Props) {
|
||||
const { setPermissions } = useAuth()
|
||||
|
||||
useEffect(() => {
|
||||
setPermissions(permissions)
|
||||
}, [permissions, setPermissions])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import type { PayloadRequest, Permissions } from 'payload'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { useAuth } from '../../providers/Auth/index.js'
|
||||
|
||||
export const HydrateClientUser: React.FC<{
|
||||
permissions: Permissions
|
||||
user: PayloadRequest['user']
|
||||
}> = ({ permissions, user }) => {
|
||||
const { setPermissions, setUser } = useAuth()
|
||||
|
||||
useEffect(() => {
|
||||
setUser(user)
|
||||
setPermissions(permissions)
|
||||
}, [user, permissions, setUser, setPermissions])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -38,7 +38,7 @@ export { ErrorPill } from '../../elements/ErrorPill/index.js'
|
||||
export { GenerateConfirmation } from '../../elements/GenerateConfirmation/index.js'
|
||||
export { Gutter } from '../../elements/Gutter/index.js'
|
||||
export { Hamburger } from '../../elements/Hamburger/index.js'
|
||||
export { HydrateClientUser } from '../../elements/HydrateClientUser/index.js'
|
||||
export { HydrateAuthProvider } from '../../elements/HydrateAuthProvider/index.js'
|
||||
export { ListControls } from '../../elements/ListControls/index.js'
|
||||
export { useListDrawer } from '../../elements/ListDrawer/index.js'
|
||||
export { ListSelection } from '../../elements/ListSelection/index.js'
|
||||
|
||||
@@ -33,8 +33,17 @@ const Context = createContext({} as AuthContext)
|
||||
|
||||
const maxTimeoutTime = 2147483647
|
||||
|
||||
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [user, setUser] = useState<ClientUser | null>()
|
||||
type Props = {
|
||||
children: React.ReactNode
|
||||
permissions?: Permissions
|
||||
user?: ClientUser | null
|
||||
}
|
||||
export function AuthProvider({
|
||||
children,
|
||||
permissions: initialPermissions,
|
||||
user: initialUser,
|
||||
}: Props) {
|
||||
const [user, setUser] = useState<ClientUser | null>(initialUser)
|
||||
const [tokenInMemory, setTokenInMemory] = useState<string>()
|
||||
const [tokenExpiration, setTokenExpiration] = useState<number>()
|
||||
const pathname = usePathname()
|
||||
@@ -51,7 +60,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
serverURL,
|
||||
} = config
|
||||
|
||||
const [permissions, setPermissions] = useState<Permissions>()
|
||||
const [permissions, setPermissions] = useState<Permissions>(initialPermissions)
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
const { closeAllModals, openModal } = useModal()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import type { I18nClient, Language } from '@payloadcms/translations'
|
||||
import type { ClientConfig, LanguageOptions } from 'payload'
|
||||
import type { ClientConfig, LanguageOptions, Permissions, User } from 'payload'
|
||||
|
||||
import { ModalContainer, ModalProvider } from '@faceless-ui/modal'
|
||||
import { ScrollInfoProvider } from '@faceless-ui/scroll-info'
|
||||
@@ -38,9 +38,11 @@ type Props = {
|
||||
fallbackLang: ClientConfig['i18n']['fallbackLanguage']
|
||||
languageCode: string
|
||||
languageOptions: LanguageOptions
|
||||
permissions: Permissions
|
||||
switchLanguageServerAction?: (lang: string) => Promise<void>
|
||||
theme: Theme
|
||||
translations: I18nClient['translations']
|
||||
user: User | null
|
||||
}
|
||||
|
||||
export const RootProvider: React.FC<Props> = ({
|
||||
@@ -51,9 +53,11 @@ export const RootProvider: React.FC<Props> = ({
|
||||
fallbackLang,
|
||||
languageCode,
|
||||
languageOptions,
|
||||
permissions,
|
||||
switchLanguageServerAction,
|
||||
theme,
|
||||
translations,
|
||||
user,
|
||||
}) => {
|
||||
return (
|
||||
<Fragment>
|
||||
@@ -81,7 +85,7 @@ export const RootProvider: React.FC<Props> = ({
|
||||
<ScrollInfoProvider>
|
||||
<SearchParamsProvider>
|
||||
<ModalProvider classPrefix="payload" transTime={0} zIndex="var(--z-modal)">
|
||||
<AuthProvider>
|
||||
<AuthProvider permissions={permissions} user={user}>
|
||||
<PreferencesProvider>
|
||||
<ThemeProvider cookiePrefix={config.cookiePrefix} theme={theme}>
|
||||
<ParamsProvider>
|
||||
|
||||
@@ -158,7 +158,7 @@ describe('auth', () => {
|
||||
test('should have up-to-date user in `useAuth` hook', async () => {
|
||||
await page.goto(url.account)
|
||||
await page.waitForURL(url.account)
|
||||
await expect(page.locator('#users-api-result')).toHaveText('')
|
||||
await expect(page.locator('#users-api-result')).toHaveText('Hello, world!')
|
||||
await expect(page.locator('#use-auth-result')).toHaveText('Hello, world!')
|
||||
const field = page.locator('#field-custom')
|
||||
await field.fill('Goodbye, world!')
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
* - specify locales to show
|
||||
*/
|
||||
|
||||
import type { Page } from '@playwright/test'
|
||||
import type { BrowserContext, Page } from '@playwright/test'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
import path from 'path'
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
initPageConsoleErrorCatch,
|
||||
saveDocAndAssert,
|
||||
selectTableRow,
|
||||
throttleTest,
|
||||
} from '../helpers.js'
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
||||
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
||||
@@ -86,6 +87,8 @@ const waitForAutoSaveToRunAndComplete = async (page: Page) => {
|
||||
await waitForAutoSaveToComplete(page)
|
||||
}
|
||||
|
||||
let context: BrowserContext
|
||||
|
||||
describe('versions', () => {
|
||||
let page: Page
|
||||
let url: AdminUrlUtil
|
||||
@@ -100,7 +103,7 @@ describe('versions', () => {
|
||||
|
||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({ dirname }))
|
||||
const context = await browser.newContext()
|
||||
context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
|
||||
initPageConsoleErrorCatch(page)
|
||||
@@ -317,14 +320,14 @@ describe('versions', () => {
|
||||
await saveDocAndAssert(page, '#action-save-draft')
|
||||
const savedDocURL = page.url()
|
||||
await page.goto(`${savedDocURL}/versions`)
|
||||
await page.waitForURL(new RegExp(`${savedDocURL}/versions`))
|
||||
await page.waitForURL(`${savedDocURL}/versions`)
|
||||
const row2 = page.locator('tbody .row-2')
|
||||
const versionID = await row2.locator('.cell-id').textContent()
|
||||
await page.goto(`${savedDocURL}/versions/${versionID}`)
|
||||
await page.waitForURL(new RegExp(`${savedDocURL}/versions/${versionID}`))
|
||||
await page.waitForURL(`${savedDocURL}/versions/${versionID}`)
|
||||
await page.locator('.restore-version__button').click()
|
||||
await page.locator('button:has-text("Confirm")').click()
|
||||
await page.waitForURL(new RegExp(savedDocURL))
|
||||
await page.waitForURL(savedDocURL)
|
||||
await expect(page.locator('#field-title')).toHaveValue('v1')
|
||||
})
|
||||
|
||||
@@ -417,9 +420,7 @@ describe('versions', () => {
|
||||
const spanishTitle = 'spanish title'
|
||||
const newDescription = 'new description'
|
||||
await page.goto(autosaveURL.create)
|
||||
// gets redirected from /create to /slug/id due to autosave
|
||||
await page.waitForURL(new RegExp(`${autosaveURL.edit('')}`))
|
||||
await wait(500)
|
||||
await waitForAutoSaveToComplete(page)
|
||||
const titleField = page.locator('#field-title')
|
||||
await expect(titleField).toBeEnabled()
|
||||
await titleField.fill(englishTitle)
|
||||
@@ -483,9 +484,7 @@ describe('versions', () => {
|
||||
|
||||
test('collection — autosave should only update the current document', async () => {
|
||||
await page.goto(autosaveURL.create)
|
||||
// gets redirected from /create to /slug/id due to autosave
|
||||
await page.waitForURL(new RegExp(`${autosaveURL.edit('')}`))
|
||||
await wait(500)
|
||||
await waitForAutoSaveToComplete(page)
|
||||
await expect(page.locator('#field-title')).toBeEnabled()
|
||||
await page.locator('#field-title').fill('first post title')
|
||||
await expect(page.locator('#field-description')).toBeEnabled()
|
||||
@@ -493,8 +492,6 @@ describe('versions', () => {
|
||||
await saveDocAndAssert(page)
|
||||
await waitForAutoSaveToComplete(page) // Make sure nothing is auto-saving before next steps
|
||||
await page.goto(autosaveURL.create)
|
||||
// gets redirected from /create to /slug/id due to autosave
|
||||
await page.waitForURL(new RegExp(`${autosaveURL.edit('')}`))
|
||||
await waitForAutoSaveToComplete(page) // Make sure nothing is auto-saving before next steps
|
||||
await wait(500)
|
||||
await expect(page.locator('#field-title')).toBeEnabled()
|
||||
|
||||
Reference in New Issue
Block a user