diff --git a/packages/next/src/index.ts b/packages/next/src/index.ts index 9648c8ba4..987243c05 100644 --- a/packages/next/src/index.ts +++ b/packages/next/src/index.ts @@ -1,5 +1,4 @@ export { AdminLayout } from './layouts/Admin' -export { DocumentLayout } from './layouts/Document' export { RootLayout } from './layouts/Root' export { Dashboard as DashboardPage } from './views/Dashboard' export { Login } from './views/Login' diff --git a/packages/next/src/layouts/Document/index.tsx b/packages/next/src/layouts/Document/index.tsx deleted file mode 100644 index 87e844b74..000000000 --- a/packages/next/src/layouts/Document/index.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import type { SanitizedConfig } from 'payload/types' - -import { DocumentHeader } from '@payloadcms/ui' -import '@payloadcms/ui/scss/app.scss' -import React, { Fragment } from 'react' - -import { initPage } from '../../utilities/initPage' - -export const metadata = { - description: 'Generated by Next.js', - title: 'Next.js', -} - -export const DocumentLayout = async ({ - children, - collectionSlug, - config: configPromise, - globalSlug, -}: { - children: React.ReactNode - collectionSlug?: string - config: Promise - globalSlug?: string -}) => { - const { collectionConfig, globalConfig, req } = await initPage({ - collectionSlug, - config: configPromise, - globalSlug, - }) - - return ( - - - {children} - - ) -} diff --git a/packages/next/src/routes/rest/collections/find.ts b/packages/next/src/routes/rest/collections/find.ts index 8496144ad..3d40f73a5 100644 --- a/packages/next/src/routes/rest/collections/find.ts +++ b/packages/next/src/routes/rest/collections/find.ts @@ -9,7 +9,6 @@ import type { CollectionRouteHandler } from '../types' export const find: CollectionRouteHandler = async ({ collection, req }) => { const { searchParams } = req - // parse using `qs` to handle `where` queries const { depth, draft, limit, page, sort, where } = qs.parse(searchParams.toString()) as { depth?: string diff --git a/packages/next/src/utilities/initPage.ts b/packages/next/src/utilities/initPage.ts index 375fcd82c..6a5d6785c 100644 --- a/packages/next/src/utilities/initPage.ts +++ b/packages/next/src/utilities/initPage.ts @@ -19,10 +19,7 @@ import { auth } from './auth' import { getRequestLanguage } from './getRequestLanguage' type Args = { - collectionSlug?: string config: Promise | SanitizedConfig - globalSlug?: string - localeParam?: string redirectUnauthenticatedUser?: boolean route?: string searchParams?: { [key: string]: string | string[] | undefined } @@ -30,13 +27,12 @@ type Args = { export const initPage = async ({ config: configPromise, - localeParam, redirectUnauthenticatedUser = false, route, searchParams, }: Args): Promise => { const headers = getHeaders() - + const localeParam = searchParams?.locale as string const { cookies, permissions, user } = await auth({ config: configPromise, headers, diff --git a/packages/next/src/views/API/RenderJSON/index.tsx b/packages/next/src/views/API/RenderJSON/index.tsx index 1cb1fec33..c004c93e4 100644 --- a/packages/next/src/views/API/RenderJSON/index.tsx +++ b/packages/next/src/views/API/RenderJSON/index.tsx @@ -14,9 +14,9 @@ const chars = { const baseClass = 'query-inspector' const Bracket = ({ + type, comma = false, position, - type, }: { comma?: boolean position: 'end' | 'start' @@ -49,7 +49,7 @@ export const RenderJSON = ({ parentType = 'object', trailingComma = false, }: Args) => { - const objectKeys = Object.keys(object) + const objectKeys = object ? Object.keys(object) : [] const objectLength = objectKeys.length const [isOpen, setIsOpen] = React.useState(true) diff --git a/packages/next/src/views/Document/index.tsx b/packages/next/src/views/Document/index.tsx index 4db57eb0c..76a735319 100644 --- a/packages/next/src/views/Document/index.tsx +++ b/packages/next/src/views/Document/index.tsx @@ -24,8 +24,9 @@ import queryString from 'qs' import React, { Fragment } from 'react' import type { AdminViewProps } from '../Root' +import type { GenerateEditViewMetadata } from './getMetaBySegment' -import { GenerateEditViewMetadata, getMetaBySegment } from './getMetaBySegment' +import { getMetaBySegment } from './getMetaBySegment' import { getViewsFromConfig } from './getViewsFromConfig' export const generateMetadata: GenerateEditViewMetadata = async (args) => getMetaBySegment(args) @@ -178,6 +179,7 @@ export const Document: React.FC = async ({ locale: locale.code, uploadEdits: undefined, } + console.log('server code', `${action}?${queryString.stringify(formQueryParams)}`) const componentProps: ServerSideEditViewProps = { id, @@ -208,7 +210,7 @@ export const Document: React.FC = async ({ /> = ({ initPageResult }) => />


- diff --git a/packages/next/src/views/List/Default/index.tsx b/packages/next/src/views/List/Default/index.tsx index 4727cf960..970814e44 100644 --- a/packages/next/src/views/List/Default/index.tsx +++ b/packages/next/src/views/List/Default/index.tsx @@ -25,6 +25,7 @@ import { SetViewActions, UnpublishMany, } from '@payloadcms/ui/elements' +import Link from 'next/link' import { formatFilesize } from 'payload/utilities' import React, { Fragment, useEffect } from 'react' @@ -156,7 +157,7 @@ export const DefaultListView: React.FC = () => {

{i18n.t('general:noResults', { label: getTranslation(labels?.plural, i18n) })}

{hasCreatePermission && newDocumentURL && ( - diff --git a/packages/next/src/views/ResetPassword/index.tsx b/packages/next/src/views/ResetPassword/index.tsx index ab5cc4984..e14f89d8f 100644 --- a/packages/next/src/views/ResetPassword/index.tsx +++ b/packages/next/src/views/ResetPassword/index.tsx @@ -61,7 +61,7 @@ export const ResetPassword: React.FC = ({ initPageResult, params />


-
diff --git a/packages/next/src/views/Unauthorized/UnauthorizedClient.tsx b/packages/next/src/views/Unauthorized/UnauthorizedClient.tsx index 49348b39a..0e65d5a28 100644 --- a/packages/next/src/views/Unauthorized/UnauthorizedClient.tsx +++ b/packages/next/src/views/Unauthorized/UnauthorizedClient.tsx @@ -1,5 +1,6 @@ 'use client' import { Button, useTranslation } from '@payloadcms/ui' +import Link from 'next/link' import React from 'react' export const UnauthorizedClient: React.FC<{ logoutRoute: string }> = ({ logoutRoute }) => { @@ -10,7 +11,7 @@ export const UnauthorizedClient: React.FC<{ logoutRoute: string }> = ({ logoutRo

{t('error:unauthorized')}

{t('error:notAllowedToAccessPage')}


- diff --git a/packages/ui/src/elements/Button/index.tsx b/packages/ui/src/elements/Button/index.tsx index 12795be1d..7180dd6b6 100644 --- a/packages/ui/src/elements/Button/index.tsx +++ b/packages/ui/src/elements/Button/index.tsx @@ -49,6 +49,7 @@ export const ButtonContents = ({ children, icon, showTooltip, tooltip }) => { export const Button = forwardRef((props, ref) => { const { id, + type = 'button', Link, 'aria-label': ariaLabel, buttonStyle = 'primary', @@ -65,7 +66,6 @@ export const Button = forwardRef(( size = 'medium', to, tooltip, - type = 'button', url, } = props @@ -95,6 +95,7 @@ export const Button = forwardRef(( const buttonProps = { id, + type, 'aria-disabled': disabled, 'aria-label': ariaLabel, className: classes, @@ -104,13 +105,12 @@ export const Button = forwardRef(( onMouseLeave: tooltip ? () => setShowTooltip(false) : undefined, rel: newTab ? 'noopener noreferrer' : undefined, target: newTab ? '_blank' : undefined, - type, } switch (el) { case 'link': if (!Link) { - console.error('Link is required when using el="link"') + console.error('Link is required when using el="link"', children) return null } diff --git a/packages/ui/src/elements/Localizer/index.tsx b/packages/ui/src/elements/Localizer/index.tsx index 8187826de..31b76e8d4 100644 --- a/packages/ui/src/elements/Localizer/index.tsx +++ b/packages/ui/src/elements/Localizer/index.tsx @@ -1,5 +1,5 @@ import { getTranslation } from '@payloadcms/translations' -import qs from 'qs' +import { useRouter } from 'next/navigation' import React from 'react' import { useConfig } from '../../providers/Config' @@ -23,9 +23,7 @@ const Localizer: React.FC<{ const { i18n } = useTranslation() const locale = useLocale() const { searchParams } = useSearchParams() - - const localeLabel = getTranslation(locale.label, i18n) - + const router = useRouter() if (localization) { const { locales } = localization @@ -36,42 +34,28 @@ const Localizer: React.FC<{ horizontalAlign="right" render={({ close }) => ( - - {locale ? ( + {locales.map((localeOption) => { + const newParams = { + ...searchParams, + locale: localeOption.code, + } + const localeOptionLabel = getTranslation(localeOption.label, i18n) + + return ( { + close() + router.refresh() }} - key={locale.code} - onClick={close} > - {localeLabel} - {localeLabel !== locale.code && ` (${locale.code})`} + {localeOptionLabel} + {localeOptionLabel !== localeOption.code && ` (${localeOption.code})`} - ) : null} - - {locales.map((localeOption) => { - if (locale.code === localeOption.code) return null - - const newParams = { - ...searchParams, - locale: localeOption.code, - } - const search = qs.stringify(newParams) - const localeOptionLabel = getTranslation(localeOption.label, i18n) - - return ( - - {localeOptionLabel} - {localeOptionLabel !== localeOption.code && ` (${localeOption.code})`} - - ) - })} - + ) + })} )} showScrollbar diff --git a/packages/ui/src/exports/providers.ts b/packages/ui/src/exports/providers.ts index 991ec77a3..fe2560346 100644 --- a/packages/ui/src/exports/providers.ts +++ b/packages/ui/src/exports/providers.ts @@ -19,6 +19,7 @@ export { EditDepthContext, EditDepthProvider } from '../providers/EditDepth' export { useEditDepth } from '../providers/EditDepth' export { FormQueryParams, FormQueryParamsProvider } from '../providers/FormQueryParams' export type { QueryParamTypes } from '../providers/FormQueryParams' +export { useFormQueryParams } from '../providers/FormQueryParams' export { useListInfo } from '../providers/ListInfo' export { ListInfoProvider } from '../providers/ListInfo' export type { ColumnPreferences } from '../providers/ListInfo/types' diff --git a/packages/ui/src/forms/Form/mergeServerFormState.ts b/packages/ui/src/forms/Form/mergeServerFormState.ts index 38a64a339..0a728e656 100644 --- a/packages/ui/src/forms/Form/mergeServerFormState.ts +++ b/packages/ui/src/forms/Form/mergeServerFormState.ts @@ -11,7 +11,7 @@ export const mergeServerFormState = ( let changed = false Object.entries(newState).forEach(([path, newFieldState]) => { - newFieldState.initialValue = oldState[path].initialValue + newFieldState.initialValue = oldState[path]?.initialValue newFieldState.value = oldState[path].value const oldErrorPaths: string[] = [] diff --git a/packages/ui/src/providers/Auth/index.tsx b/packages/ui/src/providers/Auth/index.tsx index f2085371c..2b6c9cfc7 100644 --- a/packages/ui/src/providers/Auth/index.tsx +++ b/packages/ui/src/providers/Auth/index.tsx @@ -26,7 +26,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children const [tokenInMemory, setTokenInMemory] = useState() const [tokenExpiration, setTokenExpiration] = useState() const pathname = usePathname() - const { push } = useRouter() + const router = useRouter() // const { code } = useLocale() const code = 'en' // TODO: re-enable i18n asap @@ -52,12 +52,12 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children const redirectParam = `?redirect=${encodeURIComponent( window.location.pathname.replace(admin, ''), )}` - push(`${admin}${logoutInactivityRoute}${redirectParam}`) + router.replace(`${admin}${logoutInactivityRoute}${redirectParam}`) } else { - push(`${admin}${logoutInactivityRoute}`) + router.replace(`${admin}${logoutInactivityRoute}`) } closeAllModals() - }, [push, admin, logoutInactivityRoute, closeAllModals]) + }, [router, admin, logoutInactivityRoute, closeAllModals]) const revokeTokenAndExpire = useCallback(() => { setTokenInMemory(undefined) @@ -212,7 +212,9 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children if (autoLoginJson?.token) { setTokenAndExpiration(autoLoginJson) } - push(typeof searchParams['redirect'] === 'string' ? searchParams['redirect'] : admin) + router.push( + typeof searchParams['redirect'] === 'string' ? searchParams['redirect'] : admin, + ) } else { setUser(null) revokeTokenAndExpire() @@ -232,7 +234,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children i18n.language, autoLogin, setTokenAndExpiration, - push, + router, searchParams, admin, revokeTokenAndExpire, diff --git a/packages/ui/src/providers/Locale/index.tsx b/packages/ui/src/providers/Locale/index.tsx index ef7fa284f..a3c3629a6 100644 --- a/packages/ui/src/providers/Locale/index.tsx +++ b/packages/ui/src/providers/Locale/index.tsx @@ -2,6 +2,8 @@ import type { Locale } from 'payload/config' +// TODO: abstract the `next/navigation` dependency out from this component +import { useRouter } from 'next/navigation' import React, { createContext, useContext, useEffect, useState } from 'react' import { findLocaleFromCode } from '../../utilities/findLocaleFromCode' @@ -20,7 +22,8 @@ export const LocaleProvider: React.FC<{ children?: React.ReactNode }> = ({ child const defaultLocale = localization && localization.defaultLocale ? localization.defaultLocale : 'en' - const { searchParams } = useSearchParams() + const { dispatchSearchParams, searchParams } = useSearchParams() + const router = useRouter() const [localeCode, setLocaleCode] = useState( (searchParams?.locale as string) || defaultLocale, @@ -35,20 +38,20 @@ export const LocaleProvider: React.FC<{ children?: React.ReactNode }> = ({ child const localeFromParams = searchParams.locale useEffect(() => { - if (!localization) { - return - } + async function localeChangeHandler() { + if (!localization) { + return + } - // set locale from search param - if (localeFromParams && localization.localeCodes.indexOf(localeFromParams as string) > -1) { - setLocaleCode(localeFromParams as string) - setLocale(findLocaleFromCode(localization, localeFromParams as string)) - if (user) setPreference('locale', localeFromParams) - return - } + // set locale from search param + if (localeFromParams && localization.localeCodes.indexOf(localeFromParams as string) > -1) { + setLocaleCode(localeFromParams as string) + setLocale(findLocaleFromCode(localization, localeFromParams as string)) + if (user) await setPreference('locale', localeFromParams) + return + } - // set locale from preferences or default - ;(async () => { + // set locale from preferences or default let preferenceLocale: string let isPreferenceInConfig: boolean if (user) { @@ -60,12 +63,25 @@ export const LocaleProvider: React.FC<{ children?: React.ReactNode }> = ({ child setLocale(findLocaleFromCode(localization, preferenceLocale)) return } - setPreference('locale', defaultLocale) + await setPreference('locale', defaultLocale) } setLocaleCode(defaultLocale) setLocale(findLocaleFromCode(localization, defaultLocale)) - })() - }, [defaultLocale, getPreference, localeFromParams, setPreference, user, localization]) + } + + void localeChangeHandler() + }, [defaultLocale, getPreference, localeFromParams, setPreference, user, localization, router]) + + useEffect(() => { + if (searchParams?.locale) { + dispatchSearchParams({ + type: 'set', + params: { + locale: searchParams.locale, + }, + }) + } + }, [searchParams.locale, dispatchSearchParams]) return {children} } diff --git a/packages/ui/src/providers/SearchParams/index.tsx b/packages/ui/src/providers/SearchParams/index.tsx index 220a6a77a..71b6520a1 100644 --- a/packages/ui/src/providers/SearchParams/index.tsx +++ b/packages/ui/src/providers/SearchParams/index.tsx @@ -24,6 +24,7 @@ export const SearchParamsProvider: React.FC<{ children?: React.ReactNode }> = ({ const [searchParams, dispatchSearchParams] = React.useReducer((state: State, action: Action) => { const stackAction = action.browserHistory || 'push' let paramsToSet + switch (action.type) { case 'set': paramsToSet = { diff --git a/test/_community/config.ts b/test/_community/config.ts index 553400dec..d8b514c56 100644 --- a/test/_community/config.ts +++ b/test/_community/config.ts @@ -13,7 +13,30 @@ export default buildConfigWithDefaults({ PostsCollection, MediaCollection, // ...add more collections here + { + slug: 'localized', + fields: [ + { + name: 'text', + type: 'text', + localized: true, + }, + ], + }, ], + localization: { + defaultLocale: 'en', + locales: [ + { + code: 'en', + label: 'English', + }, + { + code: 'es', + label: 'Spanish', + }, + ], + }, globals: [ MenuGlobal, // ...add more globals here