Files
payloadcms/packages/next/src/views/Version/Default/index.tsx
Jacob Fletcher 355bd12c61 chore: infer React context providers and prefer use (#11669)
As of [React 19](https://react.dev/blog/2024/12/05/react-19), context
providers no longer require the `<MyContext.Provider>` syntax and can be
rendered as `<MyContext>` directly. This will be deprecated in future
versions of React, which is now being caught by the
[`@eslint-react/no-context-provider`](https://eslint-react.xyz/docs/rules/no-context-provider)
ESLint rule.

Similarly, the [`use`](https://react.dev/reference/react/use) API is now
preferred over `useContext` because it is more flexible, for example
they can be called within loops and conditional statements. See the
[`@eslint-react/no-use-context`](https://eslint-react.xyz/docs/rules/no-use-context)
ESLint rule for more details.
2025-03-12 15:48:20 -04:00

194 lines
5.9 KiB
TypeScript

'use client'
import type { OptionObject } from 'payload'
import {
CheckboxInput,
Gutter,
useConfig,
useDocumentInfo,
useRouteTransition,
useTranslation,
} from '@payloadcms/ui'
import { formatDate } from '@payloadcms/ui/shared'
import { usePathname, useRouter, useSearchParams } from 'next/navigation.js'
import React, { useEffect, useMemo, useState } from 'react'
import type { CompareOption, DefaultVersionsViewProps } from './types.js'
import Restore from '../Restore/index.js'
import { SelectComparison } from '../SelectComparison/index.js'
import './index.scss'
import { SelectLocales } from '../SelectLocales/index.js'
import { SelectedLocalesContext } from './SelectedLocalesContext.js'
import { SetStepNav } from './SetStepNav.js'
const baseClass = 'view-version'
export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
canUpdate,
doc,
latestDraftVersion,
latestPublishedVersion,
modifiedOnly: modifiedOnlyProp,
RenderedDiff,
selectedLocales: selectedLocalesProp,
versionID,
}) => {
const { config, getEntityConfig } = useConfig()
const availableLocales = useMemo(
() =>
config.localization
? config.localization.locales.map((locale) => ({
label: locale.label,
value: locale.code,
}))
: [],
[config.localization],
)
const { i18n } = useTranslation()
const { id, collectionSlug, globalSlug } = useDocumentInfo()
const { startRouteTransition } = useRouteTransition()
const [collectionConfig] = useState(() => getEntityConfig({ collectionSlug }))
const [globalConfig] = useState(() => getEntityConfig({ globalSlug }))
const [selectedLocales, setSelectedLocales] = useState<OptionObject[]>(selectedLocalesProp)
const [compareValue, setCompareValue] = useState<CompareOption>()
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
const [modifiedOnly, setModifiedOnly] = useState(modifiedOnlyProp)
function onToggleModifiedOnly() {
setModifiedOnly(!modifiedOnly)
}
useEffect(() => {
// If the selected comparison doc or locales change, update URL params so that version page
// This is so that RSC can update the version comparison state
const current = new URLSearchParams(Array.from(searchParams.entries()))
if (!compareValue) {
current.delete('compareValue')
} else {
current.set('compareValue', compareValue?.value)
}
if (!selectedLocales) {
current.delete('localeCodes')
} else {
current.set('localeCodes', JSON.stringify(selectedLocales.map((locale) => locale.value)))
}
if (!modifiedOnly) {
current.delete('modifiedOnly')
} else {
current.set('modifiedOnly', 'true')
}
const search = current.toString()
const query = search ? `?${search}` : ''
// TODO: this transition occurs multiple times during the initial rendering phases, need to evaluate
startRouteTransition(() => router.push(`${pathname}${query}`))
}, [
compareValue,
pathname,
router,
searchParams,
selectedLocales,
modifiedOnly,
startRouteTransition,
])
const {
admin: { dateFormat },
localization,
routes: { api: apiRoute },
serverURL,
} = config
const versionCreatedAt = doc?.updatedAt
? formatDate({ date: doc.updatedAt, i18n, pattern: dateFormat })
: ''
const compareBaseURL = `${serverURL}${apiRoute}/${globalSlug ? 'globals/' : ''}${
collectionSlug || globalSlug
}/versions`
const draftsEnabled = Boolean((collectionConfig || globalConfig)?.versions.drafts)
return (
<main className={baseClass}>
<SetStepNav
collectionConfig={collectionConfig}
collectionSlug={collectionSlug}
doc={doc}
fields={(collectionConfig || globalConfig)?.fields}
globalConfig={globalConfig}
globalSlug={globalSlug}
id={id}
/>
<Gutter className={`${baseClass}__wrap`}>
<div className={`${baseClass}__header-wrap`}>
<p className={`${baseClass}__created-at`}>
{i18n.t('version:versionCreatedOn', {
version: i18n.t(doc?.autosave ? 'version:autosavedVersion' : 'version:version'),
})}
</p>
<header className={`${baseClass}__header`}>
<h2>{versionCreatedAt}</h2>
{canUpdate && (
<Restore
className={`${baseClass}__restore`}
collectionSlug={collectionSlug}
globalSlug={globalSlug}
label={collectionConfig?.labels.singular || globalConfig?.label}
originalDocID={id}
status={doc?.version?._status}
versionDate={versionCreatedAt}
versionID={versionID}
/>
)}
<span className={`${baseClass}__modifiedCheckBox`}>
<CheckboxInput
checked={modifiedOnly}
id={'modifiedOnly'}
label={i18n.t('version:modifiedOnly')}
onToggle={onToggleModifiedOnly}
/>
</span>
</header>
</div>
<div className={`${baseClass}__controls`}>
<SelectComparison
baseURL={compareBaseURL}
draftsEnabled={draftsEnabled}
latestDraftVersion={latestDraftVersion}
latestPublishedVersion={latestPublishedVersion}
onChange={setCompareValue}
parentID={id}
value={compareValue}
versionID={versionID}
/>
{localization && (
<SelectLocales
onChange={setSelectedLocales}
options={availableLocales}
value={selectedLocales}
/>
)}
</div>
<SelectedLocalesContext
value={{ selectedLocales: selectedLocales.map((locale) => locale.value) }}
>
{doc?.version && RenderedDiff}
</SelectedLocalesContext>
</Gutter>
</main>
)
}