From 1d81b0c6dd7bcc8b1a47037d8bae6f94893a830c Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Mon, 11 Aug 2025 16:59:03 -0400 Subject: [PATCH 01/14] fix(ui): autosave hooks are not reflected in form state (#13416) Fixes #10515. Needed for #12956. Hooks run within autosave are not reflected in form state. Similar to #10268, but for autosave events. For example, if you are using a computed value, like this: ```ts [ // ... { name: 'title', type: 'text', }, { name: 'computedTitle', type: 'text', hooks: { beforeChange: [({ data }) => data?.title], }, }, ] ``` In the example above, when an autosave event is triggered after changing the `title` field, we expect the `computedTitle` field to match. But although this takes place on the database level, the UI does not reflect this change unless you refresh the page or navigate back and forth. Here's an example: Before: https://github.com/user-attachments/assets/c8c68a78-9957-45a8-a710-84d954d15bcc After: https://github.com/user-attachments/assets/16cb87a5-83ca-4891-b01f-f5c4b0a34362 --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1210561273449855 --- docs/admin/react-hooks.mdx | 2 +- packages/ui/src/elements/Autosave/index.tsx | 146 ++++-------------- .../src/elements/DocumentDrawer/Provider.tsx | 5 + packages/ui/src/elements/Upload/index.tsx | 32 ++-- packages/ui/src/forms/Form/index.tsx | 31 +++- packages/ui/src/forms/Form/types.ts | 28 +++- .../ui/src/providers/DocumentInfo/index.tsx | 23 ++- .../ui/src/providers/DocumentInfo/types.ts | 21 ++- .../ui/src/providers/DocumentTitle/index.tsx | 7 +- packages/ui/src/views/Edit/index.tsx | 42 +++-- test/_community/payload-types.ts | 26 ++-- test/versions/collections/Autosave.ts | 8 + test/versions/e2e.spec.ts | 38 +++++ test/versions/payload-types.ts | 6 +- 14 files changed, 214 insertions(+), 201 deletions(-) diff --git a/docs/admin/react-hooks.mdx b/docs/admin/react-hooks.mdx index 5640f185ca..2d021daf20 100644 --- a/docs/admin/react-hooks.mdx +++ b/docs/admin/react-hooks.mdx @@ -739,7 +739,7 @@ The `useDocumentInfo` hook provides information about the current document being | **`lastUpdateTime`** | Timestamp of the last update to the document. | | **`mostRecentVersionIsAutosaved`** | Whether the most recent version is an autosaved version. | | **`preferencesKey`** | The `preferences` key to use when interacting with document-level user preferences. [More details](./preferences). | -| **`savedDocumentData`** | The saved data of the document. | +| **`data`** | The saved data of the document. | | **`setDocFieldPreferences`** | Method to set preferences for a specific field. [More details](./preferences). | | **`setDocumentTitle`** | Method to set the document title. | | **`setHasPublishedDoc`** | Method to update whether the document has been published. | diff --git a/packages/ui/src/elements/Autosave/index.tsx b/packages/ui/src/elements/Autosave/index.tsx index 2215f7e659..f341efa21e 100644 --- a/packages/ui/src/elements/Autosave/index.tsx +++ b/packages/ui/src/elements/Autosave/index.tsx @@ -5,7 +5,6 @@ import type { ClientCollectionConfig, ClientGlobalConfig } from 'payload' import { dequal } from 'dequal/lite' import { reduceFieldsToValues, versionDefaults } from 'payload/shared' import React, { useDeferredValue, useEffect, useRef, useState } from 'react' -import { toast } from 'sonner' import { useAllFormFields, @@ -17,13 +16,11 @@ import { useDebounce } from '../../hooks/useDebounce.js' import { useEffectEvent } from '../../hooks/useEffectEvent.js' import { useQueues } from '../../hooks/useQueues.js' import { useConfig } from '../../providers/Config/index.js' -import { useDocumentEvents } from '../../providers/DocumentEvents/index.js' import { useDocumentInfo } from '../../providers/DocumentInfo/index.js' import { useLocale } from '../../providers/Locale/index.js' import { useTranslation } from '../../providers/Translation/index.js' import { formatTimeToNow } from '../../utilities/formatDocTitle/formatDateTitle.js' import { reduceFieldsToValuesWithValidation } from '../../utilities/reduceFieldsToValuesWithValidation.js' -import { useDocumentDrawerContext } from '../DocumentDrawer/Provider.js' import { LeaveWithoutSaving } from '../LeaveWithoutSaving/index.js' import './index.scss' @@ -51,16 +48,11 @@ export const Autosave: React.FC = ({ id, collection, global: globalDoc }) incrementVersionCount, lastUpdateTime, mostRecentVersionIsAutosaved, - setLastUpdateTime, setMostRecentVersionIsAutosaved, setUnpublishedVersionCount, - updateSavedDocumentData, } = useDocumentInfo() - const { onSave: onSaveFromDocumentDrawer } = useDocumentDrawerContext() - - const { reportUpdate } = useDocumentEvents() - const { dispatchFields, isValid, setBackgroundProcessing, setIsValid } = useForm() + const { isValid, setBackgroundProcessing, submit } = useForm() const [formState] = useAllFormFields() const modified = useFormModified() @@ -151,118 +143,38 @@ export const Autosave: React.FC = ({ id, collection, global: globalDoc }) method = 'POST' } - if (url) { - if (modifiedRef.current) { - const { data, valid } = reduceFieldsToValuesWithValidation(formStateRef.current, true) + const { valid } = reduceFieldsToValuesWithValidation(formStateRef.current, true) - data._status = 'draft' + const skipSubmission = + submitted && !valid && versionsConfig?.drafts && versionsConfig?.drafts?.validate - const skipSubmission = - submitted && !valid && versionsConfig?.drafts && versionsConfig?.drafts?.validate + if (!skipSubmission && modifiedRef.current && url) { + const result = await submit({ + action: url, + context: { + incrementVersionCount: false, + }, + disableFormWhileProcessing: false, + disableSuccessStatus: true, + method, + overrides: { + _status: 'draft', + }, + skipValidation: versionsConfig?.drafts && !versionsConfig?.drafts?.validate, + }) - if (!skipSubmission) { - let res - - try { - res = await fetch(url, { - body: JSON.stringify(data), - credentials: 'include', - headers: { - 'Accept-Language': i18n.language, - 'Content-Type': 'application/json', - }, - method, - }) - } catch (_err) { - // Swallow Error - } - - const newDate = new Date() - // We need to log the time in order to figure out if we need to trigger the state off later - endTimestamp = newDate.getTime() - - const json = await res.json() - - if (res.status === 200) { - setLastUpdateTime(newDate.getTime()) - - reportUpdate({ - id, - entitySlug, - updatedAt: newDate.toISOString(), - }) - - // if onSaveFromDocumentDrawer is defined, call it - if (typeof onSaveFromDocumentDrawer === 'function') { - void onSaveFromDocumentDrawer({ - ...json, - operation: 'update', - }) - } - - if (!mostRecentVersionIsAutosaved) { - incrementVersionCount() - setMostRecentVersionIsAutosaved(true) - setUnpublishedVersionCount((prev) => prev + 1) - } - } - - if (versionsConfig?.drafts && versionsConfig?.drafts?.validate && json?.errors) { - if (Array.isArray(json.errors)) { - const [fieldErrors, nonFieldErrors] = json.errors.reduce( - ([fieldErrs, nonFieldErrs], err) => { - const newFieldErrs = [] - const newNonFieldErrs = [] - - if (err?.message) { - newNonFieldErrs.push(err) - } - - if (Array.isArray(err?.data)) { - err.data.forEach((dataError) => { - if (dataError?.field) { - newFieldErrs.push(dataError) - } else { - newNonFieldErrs.push(dataError) - } - }) - } - - return [ - [...fieldErrs, ...newFieldErrs], - [...nonFieldErrs, ...newNonFieldErrs], - ] - }, - [[], []], - ) - - dispatchFields({ - type: 'ADD_SERVER_ERRORS', - errors: fieldErrors, - }) - - nonFieldErrors.forEach((err) => { - toast.error(err.message || i18n.t('error:unknown')) - }) - - setIsValid(false) - hideIndicator() - return - } - } else { - // If it's not an error then we can update the document data inside the context - const document = json?.doc || json?.result - - // Manually update the data since this function doesn't fire the `submit` function from useForm - if (document) { - setIsValid(true) - updateSavedDocumentData(document) - } - } - - hideIndicator() - } + if (result && result?.res?.ok && !mostRecentVersionIsAutosaved) { + incrementVersionCount() + setMostRecentVersionIsAutosaved(true) + setUnpublishedVersionCount((prev) => prev + 1) } + + const newDate = new Date() + + // We need to log the time in order to figure out if we need to trigger the state off later + endTimestamp = newDate.getTime() + + hideIndicator() } } }, diff --git a/packages/ui/src/elements/DocumentDrawer/Provider.tsx b/packages/ui/src/elements/DocumentDrawer/Provider.tsx index 6b6c8c2b51..1bc4669053 100644 --- a/packages/ui/src/elements/DocumentDrawer/Provider.tsx +++ b/packages/ui/src/elements/DocumentDrawer/Provider.tsx @@ -20,6 +20,11 @@ export type DocumentDrawerContextProps = { }) => Promise | void readonly onSave?: (args: { collectionConfig?: ClientCollectionConfig + /** + * @experimental - Note: this property is experimental and may change in the future. Use as your own discretion. + * If you want to pass additional data to the onSuccess callback, you can use this context object. + */ + context?: Record doc: TypeWithID operation: 'create' | 'update' result: Data diff --git a/packages/ui/src/elements/Upload/index.tsx b/packages/ui/src/elements/Upload/index.tsx index e9f0c92719..475a9efd28 100644 --- a/packages/ui/src/elements/Upload/index.tsx +++ b/packages/ui/src/elements/Upload/index.tsx @@ -161,7 +161,7 @@ export const Upload_v4: React.FC = (props) => { const { t } = useTranslation() const { setModified } = useForm() - const { id, docPermissions, savedDocumentData, setUploadStatus } = useDocumentInfo() + const { id, data, docPermissions, setUploadStatus } = useDocumentInfo() const isFormSubmitting = useFormProcessing() const { errorMessage, setValue, showError, value } = useField({ path: 'file', @@ -349,7 +349,7 @@ export const Upload_v4: React.FC = (props) => { const acceptMimeTypes = uploadConfig.mimeTypes?.join(', ') - const imageCacheTag = uploadConfig?.cacheTags && savedDocumentData?.updatedAt + const imageCacheTag = uploadConfig?.cacheTags && data?.updatedAt useEffect(() => { const handleControlFileUrl = async () => { @@ -375,11 +375,11 @@ export const Upload_v4: React.FC = (props) => { return (
- {savedDocumentData && savedDocumentData.filename && !removedFile && ( + {data && data.filename && !removedFile && ( = (props) => { uploadConfig={uploadConfig} /> )} - {((!uploadConfig.hideFileInputOnCreate && !savedDocumentData?.filename) || removedFile) && ( + {((!uploadConfig.hideFileInputOnCreate && !data?.filename) || removedFile) && (
{!value && !showUrlInput && ( @@ -506,7 +506,7 @@ export const Upload_v4: React.FC = (props) => {
@@ -523,17 +523,17 @@ export const Upload_v4: React.FC = (props) => { )}
)} - {(value || savedDocumentData?.filename) && ( + {(value || data?.filename) && ( = (props) => { )} - {savedDocumentData && hasImageSizes && ( + {data && hasImageSizes && ( - + )} diff --git a/packages/ui/src/forms/Form/index.tsx b/packages/ui/src/forms/Form/index.tsx index 79e12b29ff..ddb1cb115d 100644 --- a/packages/ui/src/forms/Form/index.tsx +++ b/packages/ui/src/forms/Form/index.tsx @@ -18,6 +18,7 @@ import type { Context as FormContextType, FormProps, GetDataByPath, + Submit, SubmitOptions, } from './types.js' @@ -199,14 +200,19 @@ export const Form: React.FC = (props) => { return isValid }, [collectionSlug, config, dispatchFields, id, operation, t, user, documentForm]) - const submit = useCallback( - async (options: SubmitOptions = {}, e): Promise => { + const submit = useCallback( + async (options, e) => { const { action: actionArg = action, + context, + disableFormWhileProcessing = true, + disableSuccessStatus: disableSuccessStatusFromArgs, method: methodToUse = method, overrides: overridesFromArgs = {}, skipValidation, - } = options + } = options || ({} as SubmitOptions) + + const disableToast = disableSuccessStatusFromArgs ?? disableSuccessStatus if (disabled) { if (e) { @@ -217,6 +223,7 @@ export const Form: React.FC = (props) => { // create new toast promise which will resolve manually later let errorToast, successToast + const promise = new Promise((resolve, reject) => { successToast = resolve errorToast = reject @@ -225,7 +232,7 @@ export const Form: React.FC = (props) => { const hasFormSubmitAction = actionArg || typeof action === 'string' || typeof action === 'function' - if (redirect || disableSuccessStatus || !hasFormSubmitAction) { + if (redirect || disableToast || !hasFormSubmitAction) { // Do not show submitting toast, as the promise toast may never disappear under these conditions. // Instead, make successToast() or errorToast() throw toast.success / toast.error successToast = (data) => toast.success(data) @@ -247,8 +254,10 @@ export const Form: React.FC = (props) => { e.preventDefault() } - setProcessing(true) - setDisabled(true) + if (disableFormWhileProcessing) { + setProcessing(true) + setDisabled(true) + } if (waitForAutocomplete) { await wait(100) @@ -290,6 +299,7 @@ export const Form: React.FC = (props) => { skipValidation || disableValidationOnSubmit ? true : await contextRef.current.validateForm() setIsValid(isValid) + // If not valid, prevent submission if (!isValid) { errorToast(t('error:correctInvalidFields')) @@ -366,9 +376,10 @@ export const Form: React.FC = (props) => { if (isJSON) { json = await res.json() } + if (res.status < 400) { if (typeof onSuccess === 'function') { - const newFormState = await onSuccess(json) + const newFormState = await onSuccess(json, context) if (newFormState) { dispatchFields({ @@ -379,12 +390,13 @@ export const Form: React.FC = (props) => { }) } } + setSubmitted(false) setProcessing(false) if (redirect) { startRouteTransition(() => router.push(redirect)) - } else if (!disableSuccessStatus) { + } else if (!disableToast) { successToast(json.message || t('general:submissionSuccessful')) } } else { @@ -392,6 +404,7 @@ export const Form: React.FC = (props) => { setSubmitted(true) contextRef.current = { ...contextRef.current } // triggers rerender of all components that subscribe to form + if (json.message) { errorToast(json.message) return @@ -443,6 +456,8 @@ export const Form: React.FC = (props) => { errorToast(message) } + + return { formState: contextRef.current.fields, res } } catch (err) { console.error('Error submitting form', err) // eslint-disable-line no-console setProcessing(false) diff --git a/packages/ui/src/forms/Form/types.ts b/packages/ui/src/forms/Form/types.ts index 9cfd1ef644..a10350506a 100644 --- a/packages/ui/src/forms/Form/types.ts +++ b/packages/ui/src/forms/Form/types.ts @@ -52,7 +52,7 @@ export type FormProps = { log?: boolean onChange?: ((args: { formState: FormState; submitted?: boolean }) => Promise)[] onSubmit?: (fields: FormState, data: Data) => void - onSuccess?: (json: unknown) => Promise | void + onSuccess?: (json: unknown, context?: Record) => Promise | void redirect?: string submitted?: boolean uuid?: string @@ -70,16 +70,40 @@ export type FormProps = { export type SubmitOptions = { action?: string + /** + * @experimental - Note: this property is experimental and may change in the future. Use as your own discretion. + * If you want to pass additional data to the onSuccess callback, you can use this context object. + */ + context?: Record + /** + * When true, will disable the form while it is processing. + * @default true + */ + disableFormWhileProcessing?: boolean + /** + * When true, will disable the success toast after form submission. + * @default false + */ + disableSuccessStatus?: boolean method?: string overrides?: ((formState) => FormData) | Record + /** + * When true, will skip validation before submitting the form. + * @default false + */ skipValidation?: boolean } export type DispatchFields = React.Dispatch + export type Submit = ( options?: SubmitOptions, e?: React.FormEvent, -) => Promise +) => Promise export type ValidateForm = () => Promise diff --git a/packages/ui/src/providers/DocumentInfo/index.tsx b/packages/ui/src/providers/DocumentInfo/index.tsx index 0fdb4d9324..12ad464648 100644 --- a/packages/ui/src/providers/DocumentInfo/index.tsx +++ b/packages/ui/src/providers/DocumentInfo/index.tsx @@ -97,6 +97,7 @@ const DocumentInfo: React.FC< const [versionCount, setVersionCount] = useState(versionCountFromProps) const [hasPublishedDoc, setHasPublishedDoc] = useState(hasPublishedDocFromProps) + const [unpublishedVersionCount, setUnpublishedVersionCount] = useState( unpublishedVersionCountFromProps, ) @@ -104,11 +105,14 @@ const DocumentInfo: React.FC< const [documentIsLocked, setDocumentIsLocked] = useControllableState( isLockedFromProps, ) + const [currentEditor, setCurrentEditor] = useControllableState( currentEditorFromProps, ) const [lastUpdateTime, setLastUpdateTime] = useControllableState(lastUpdateTimeFromProps) - const [savedDocumentData, setSavedDocumentData] = useControllableState(initialData) + + const [data, setData] = useControllableState(initialData) + const [uploadStatus, setUploadStatus] = useControllableState<'failed' | 'idle' | 'uploading'>( 'idle', ) @@ -294,13 +298,6 @@ const DocumentInfo: React.FC< } }, [collectionConfig, globalConfig, versionCount]) - const updateSavedDocumentData = React.useCallback( - (json) => { - setSavedDocumentData(json) - }, - [setSavedDocumentData], - ) - /** * @todo: Remove this in v4 * Users should use the `DocumentTitleContext` instead. @@ -309,14 +306,14 @@ const DocumentInfo: React.FC< setDocumentTitle( formatDocTitle({ collectionConfig, - data: { ...savedDocumentData, id }, + data: { ...data, id }, dateFormat, fallback: id?.toString(), globalConfig, i18n, }), ) - }, [collectionConfig, globalConfig, savedDocumentData, dateFormat, i18n, id]) + }, [collectionConfig, globalConfig, data, dateFormat, i18n, id]) // clean on unmount useEffect(() => { @@ -351,6 +348,7 @@ const DocumentInfo: React.FC< ...props, action, currentEditor, + data, docConfig, docPermissions, documentIsLocked, @@ -367,8 +365,9 @@ const DocumentInfo: React.FC< lastUpdateTime, mostRecentVersionIsAutosaved, preferencesKey, - savedDocumentData, + savedDocumentData: data, setCurrentEditor, + setData, setDocFieldPreferences, setDocumentIsLocked, setDocumentTitle, @@ -381,7 +380,7 @@ const DocumentInfo: React.FC< unlockDocument, unpublishedVersionCount, updateDocumentEditor, - updateSavedDocumentData, + updateSavedDocumentData: setData, uploadStatus, versionCount, } diff --git a/packages/ui/src/providers/DocumentInfo/types.ts b/packages/ui/src/providers/DocumentInfo/types.ts index 7f9233e365..4d8d6404c2 100644 --- a/packages/ui/src/providers/DocumentInfo/types.ts +++ b/packages/ui/src/providers/DocumentInfo/types.ts @@ -49,6 +49,7 @@ export type DocumentInfoProps = { export type DocumentInfoContext = { currentEditor?: ClientUser | null | number | string + data?: Data docConfig?: ClientCollectionConfig | ClientGlobalConfig documentIsLocked?: boolean documentLockState: React.RefObject<{ @@ -61,21 +62,26 @@ export type DocumentInfoContext = { incrementVersionCount: () => void isInitializing: boolean preferencesKey?: string + /** + * @deprecated This property is deprecated and will be removed in v4. + * Use `data` instead. + */ savedDocumentData?: Data setCurrentEditor?: React.Dispatch> + setData: (data: Data) => void setDocFieldPreferences: ( field: string, fieldPreferences: { [key: string]: unknown } & Partial, ) => void setDocumentIsLocked?: React.Dispatch> /** - * * @deprecated This property is deprecated and will be removed in v4. - * This is for performance reasons. Use the `DocumentTitleContext` instead. + * This is for performance reasons. Use the `DocumentTitleContext` instead + * via the `useDocumentTitle` hook. * @example * ```tsx * import { useDocumentTitle } from '@payloadcms/ui' - * const { setDocumentTitle } = useDocumentTitle() + * const { setDocumentTitle } = useDocumentTitle() * ``` */ setDocumentTitle: React.Dispatch> @@ -86,17 +92,22 @@ export type DocumentInfoContext = { setUploadStatus?: (status: 'failed' | 'idle' | 'uploading') => void /** * @deprecated This property is deprecated and will be removed in v4. - * This is for performance reasons. Use the `DocumentTitleContext` instead. + * This is for performance reasons. Use the `DocumentTitleContext` instead + * via the `useDocumentTitle` hook. * @example * ```tsx * import { useDocumentTitle } from '@payloadcms/ui' - * const { title } = useDocumentTitle() + * const { title } = useDocumentTitle() * ``` */ title: string unlockDocument: (docID: number | string, slug: string) => Promise unpublishedVersionCount: number updateDocumentEditor: (docID: number | string, slug: string, user: ClientUser) => Promise + /** + * @deprecated This property is deprecated and will be removed in v4. + * Use `setData` instead. + */ updateSavedDocumentData: (data: Data) => void uploadStatus?: 'failed' | 'idle' | 'uploading' versionCount: number diff --git a/packages/ui/src/providers/DocumentTitle/index.tsx b/packages/ui/src/providers/DocumentTitle/index.tsx index 2294219681..1202a9ca47 100644 --- a/packages/ui/src/providers/DocumentTitle/index.tsx +++ b/packages/ui/src/providers/DocumentTitle/index.tsx @@ -19,8 +19,7 @@ export const useDocumentTitle = (): IDocumentTitleContext => use(DocumentTitleCo export const DocumentTitleProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const { id, collectionSlug, docConfig, globalSlug, initialData, savedDocumentData } = - useDocumentInfo() + const { id, collectionSlug, data, docConfig, globalSlug, initialData } = useDocumentInfo() const { config: { @@ -45,14 +44,14 @@ export const DocumentTitleProvider: React.FC<{ setDocumentTitle( formatDocTitle({ collectionConfig: collectionSlug ? (docConfig as ClientCollectionConfig) : undefined, - data: { ...savedDocumentData, id }, + data: { ...data, id }, dateFormat, fallback: id?.toString(), globalConfig: globalSlug ? (docConfig as ClientGlobalConfig) : undefined, i18n, }), ) - }, [savedDocumentData, dateFormat, i18n, id, collectionSlug, docConfig, globalSlug]) + }, [data, dateFormat, i18n, id, collectionSlug, docConfig, globalSlug]) return {children} } diff --git a/packages/ui/src/views/Edit/index.tsx b/packages/ui/src/views/Edit/index.tsx index b621fc87d9..1e78cf46ac 100644 --- a/packages/ui/src/views/Edit/index.tsx +++ b/packages/ui/src/views/Edit/index.tsx @@ -65,6 +65,7 @@ export function DefaultEditView({ BeforeFields, collectionSlug, currentEditor, + data, disableActions, disableCreate, disableLeaveWithoutSaving, @@ -86,12 +87,12 @@ export function DefaultEditView({ redirectAfterDelete, redirectAfterDuplicate, redirectAfterRestore, - savedDocumentData, setCurrentEditor, + setData, setDocumentIsLocked, + setLastUpdateTime, unlockDocument, updateDocumentEditor, - updateSavedDocumentData, } = useDocumentInfo() const { @@ -237,7 +238,7 @@ export function DefaultEditView({ setDocumentIsLocked(false) setCurrentEditor(null) } catch (err) { - console.error('Failed to unlock before leave', err) + console.error('Failed to unlock before leave', err) // eslint-disable-line no-console } } } @@ -256,15 +257,17 @@ export function DefaultEditView({ ]) const onSave = useCallback( - async (json): Promise => { + async (json, context?: Record): Promise => { const controller = handleAbortRef(abortOnSaveRef) const document = json?.doc || json?.result + const updatedAt = document?.updatedAt || new Date().toISOString() + reportUpdate({ id, entitySlug, - updatedAt: document?.updatedAt || new Date().toISOString(), + updatedAt, }) // If we're editing the doc of the logged-in user, @@ -273,10 +276,14 @@ export function DefaultEditView({ void refreshCookieAsync() } - incrementVersionCount() + setLastUpdateTime(updatedAt) - if (typeof updateSavedDocumentData === 'function') { - void updateSavedDocumentData(document || {}) + if (context?.incrementVersionCount !== false) { + incrementVersionCount() + } + + if (typeof setData === 'function') { + void setData(document || {}) } if (typeof onSaveFromContext === 'function') { @@ -284,6 +291,7 @@ export function DefaultEditView({ void onSaveFromContext({ ...json, + context, operation, updatedAt: operation === 'update' @@ -306,7 +314,7 @@ export function DefaultEditView({ await getDocPermissions(json) - if ((id || globalSlug) && !autosaveEnabled) { + if (id || globalSlug) { const docPreferences = await getDocPreferences() const { state } = await getFormState({ @@ -341,18 +349,19 @@ export function DefaultEditView({ user, collectionSlug, userSlug, - incrementVersionCount, - updateSavedDocumentData, + setLastUpdateTime, + setData, onSaveFromContext, - redirectAfterCreate, isEditing, depth, + redirectAfterCreate, getDocPermissions, globalSlug, - autosaveEnabled, refreshCookieAsync, + incrementVersionCount, adminRoute, locale, + startRouteTransition, router, resetUploadEdits, getDocPreferences, @@ -362,7 +371,6 @@ export function DefaultEditView({ schemaPathSegments, isLockingEnabled, setDocumentIsLocked, - startRouteTransition, ], ) @@ -549,7 +557,7 @@ export function DefaultEditView({ SaveButton, SaveDraftButton, }} - data={savedDocumentData} + data={data} disableActions={disableActions || isFolderCollection || isTrashed} disableCreate={disableCreate} EditMenuItems={EditMenuItems} @@ -612,14 +620,14 @@ export function DefaultEditView({ className={`${baseClass}__auth`} collectionSlug={collectionConfig.slug} disableLocalStrategy={collectionConfig.auth?.disableLocalStrategy} - email={savedDocumentData?.email} + email={data?.email} loginWithUsername={auth?.loginWithUsername} operation={operation} readOnly={!hasSavePermission} requirePassword={!id} setValidateBeforeSubmit={setValidateBeforeSubmit} useAPIKey={auth.useAPIKey} - username={savedDocumentData?.username} + username={data?.username} verify={auth.verify} /> )} diff --git a/test/_community/payload-types.ts b/test/_community/payload-types.ts index 6d7c964010..599c9dec1d 100644 --- a/test/_community/payload-types.ts +++ b/test/_community/payload-types.ts @@ -84,7 +84,7 @@ export interface Config { 'payload-migrations': PayloadMigrationsSelect | PayloadMigrationsSelect; }; db: { - defaultIDType: number; + defaultIDType: string; }; globals: { menu: Menu; @@ -124,7 +124,7 @@ export interface UserAuthOperations { * via the `definition` "posts". */ export interface Post { - id: number; + id: string; title?: string | null; content?: { root: { @@ -149,7 +149,7 @@ export interface Post { * via the `definition` "media". */ export interface Media { - id: number; + id: string; updatedAt: string; createdAt: string; url?: string | null; @@ -193,7 +193,7 @@ export interface Media { * via the `definition` "users". */ export interface User { - id: number; + id: string; updatedAt: string; createdAt: string; email: string; @@ -217,24 +217,24 @@ export interface User { * via the `definition` "payload-locked-documents". */ export interface PayloadLockedDocument { - id: number; + id: string; document?: | ({ relationTo: 'posts'; - value: number | Post; + value: string | Post; } | null) | ({ relationTo: 'media'; - value: number | Media; + value: string | Media; } | null) | ({ relationTo: 'users'; - value: number | User; + value: string | User; } | null); globalSlug?: string | null; user: { relationTo: 'users'; - value: number | User; + value: string | User; }; updatedAt: string; createdAt: string; @@ -244,10 +244,10 @@ export interface PayloadLockedDocument { * via the `definition` "payload-preferences". */ export interface PayloadPreference { - id: number; + id: string; user: { relationTo: 'users'; - value: number | User; + value: string | User; }; key?: string | null; value?: @@ -267,7 +267,7 @@ export interface PayloadPreference { * via the `definition` "payload-migrations". */ export interface PayloadMigration { - id: number; + id: string; name?: string | null; batch?: number | null; updatedAt: string; @@ -393,7 +393,7 @@ export interface PayloadMigrationsSelect { * via the `definition` "menu". */ export interface Menu { - id: number; + id: string; globalText?: string | null; updatedAt?: string | null; createdAt?: string | null; diff --git a/test/versions/collections/Autosave.ts b/test/versions/collections/Autosave.ts index ff18f7cb1b..7350ff2b87 100644 --- a/test/versions/collections/Autosave.ts +++ b/test/versions/collections/Autosave.ts @@ -53,6 +53,14 @@ const AutosavePosts: CollectionConfig = { unique: true, localized: true, }, + { + name: 'computedTitle', + label: 'Computed Title', + type: 'text', + hooks: { + beforeChange: [({ data }) => data?.title], + }, + }, { name: 'description', label: 'Description', diff --git a/test/versions/e2e.spec.ts b/test/versions/e2e.spec.ts index 13b2428d25..8973c23714 100644 --- a/test/versions/e2e.spec.ts +++ b/test/versions/e2e.spec.ts @@ -1285,6 +1285,44 @@ describe('Versions', () => { // Remove listener page.removeListener('dialog', acceptAlert) }) + + test('- with autosave - applies afterChange hooks to form state after autosave runs', async () => { + const url = new AdminUrlUtil(serverURL, autosaveCollectionSlug) + await page.goto(url.create) + const titleField = page.locator('#field-title') + await titleField.fill('Initial') + await waitForAutoSaveToRunAndComplete(page) + const computedTitleField = page.locator('#field-computedTitle') + await expect(computedTitleField).toHaveValue('Initial') + }) + + test('- with autosave - does not display success toast after autosave complete', async () => { + const url = new AdminUrlUtil(serverURL, autosaveCollectionSlug) + await page.goto(url.create) + const titleField = page.locator('#field-title') + await titleField.fill('Initial') + + let hasDisplayedToast = false + + const startTime = Date.now() + const timeout = 5000 + const interval = 100 + + while (Date.now() - startTime < timeout) { + const isHidden = await page.locator('.payload-toast-item').isHidden() + console.log(`Toast is hidden: ${isHidden}`) + + // eslint-disable-next-line playwright/no-conditional-in-test + if (!isHidden) { + hasDisplayedToast = true + break + } + + await wait(interval) + } + + expect(hasDisplayedToast).toBe(false) + }) }) describe('Globals - publish individual locale', () => { diff --git a/test/versions/payload-types.ts b/test/versions/payload-types.ts index ddfc1db287..d393912cf4 100644 --- a/test/versions/payload-types.ts +++ b/test/versions/payload-types.ts @@ -197,6 +197,7 @@ export interface Post { export interface AutosavePost { id: string; title: string; + computedTitle?: string | null; description: string; updatedAt: string; createdAt: string; @@ -366,7 +367,6 @@ export interface Diff { textInNamedTab1InBlock?: string | null; }; textInUnnamedTab2InBlock?: string | null; - textInUnnamedTab2InBlockAccessFalse?: string | null; id?: string | null; blockName?: string | null; blockType: 'TabsBlock'; @@ -469,7 +469,6 @@ export interface Diff { }; textInUnnamedTab2?: string | null; text?: string | null; - textCannotRead?: string | null; textArea?: string | null; upload?: (string | null) | Media; uploadHasMany?: (string | Media)[] | null; @@ -787,6 +786,7 @@ export interface PostsSelect { */ export interface AutosavePostsSelect { title?: T; + computedTitle?: T; description?: T; updatedAt?: T; createdAt?: T; @@ -960,7 +960,6 @@ export interface DiffSelect { textInNamedTab1InBlock?: T; }; textInUnnamedTab2InBlock?: T; - textInUnnamedTab2InBlockAccessFalse?: T; id?: T; blockName?: T; }; @@ -995,7 +994,6 @@ export interface DiffSelect { }; textInUnnamedTab2?: T; text?: T; - textCannotRead?: T; textArea?: T; upload?: T; uploadHasMany?: T; From 2bc9a2def4560c1830fdf02a1999ae5c425299a0 Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Mon, 11 Aug 2025 17:12:43 -0400 Subject: [PATCH 02/14] fix(templates): only generate slug from title on demand (#12956) Currently, the slug field is generated from the title field indefinitely, even after the document has been created and the initial slug has been assigned. This should only occur on create, however, as it currently does, or when the user explicitly requests it. Given that slugs often determine the URL structure of the webpage that the document corresponds to, they should rarely change after being published, and when they do, would require HTTP redirects, etc. to do right in a production environment. But this is also a problem with Live Preview which relies on a constant iframe src. If your Live Preview URL includes the slug as a route param, which is often the case, then changing the slug will result in a broken connection as the queried document can no longer be found. The current workaround is to save the document and refresh the page. Now, the slug is only generated on initial create, or when the user explicitly clicks the new "generate" button above the slug field. In the future we can evaluate supporting dynamic Live Preview URLs. Regenerating this URL on every change would put additional load on the client as it would have to reestablish connection every time it changes, but it should be supported still. See #13055. Discord discussion here: https://discord.com/channels/967097582721572934/1102950643259424828/1387737976892686346 Related: #10536 --- .../website/src/fields/slug/SlugComponent.tsx | 41 +++++++++---------- .../website/src/fields/slug/formatSlug.ts | 10 ++--- templates/website/src/fields/slug/index.scss | 1 + .../src/fields/slug/SlugComponent.tsx | 41 +++++++++---------- .../src/fields/slug/formatSlug.ts | 10 ++--- .../src/fields/slug/index.scss | 1 + 6 files changed, 52 insertions(+), 52 deletions(-) diff --git a/templates/website/src/fields/slug/SlugComponent.tsx b/templates/website/src/fields/slug/SlugComponent.tsx index f21ae829d2..8114973e16 100644 --- a/templates/website/src/fields/slug/SlugComponent.tsx +++ b/templates/website/src/fields/slug/SlugComponent.tsx @@ -1,5 +1,5 @@ 'use client' -import React, { useCallback, useEffect } from 'react' +import React, { useCallback } from 'react' import { TextFieldClientProps } from 'payload' import { useField, Button, TextInput, FieldLabel, useFormFields, useForm } from '@payloadcms/ui' @@ -27,21 +27,18 @@ export const SlugComponent: React.FC = ({ const { value, setValue } = useField({ path: path || field.name }) - const { dispatchFields } = useForm() + const { dispatchFields, getDataByPath } = useForm() - // The value of the checkbox - // We're using separate useFormFields to minimise re-renders - const checkboxValue = useFormFields(([fields]) => { + const isLocked = useFormFields(([fields]) => { return fields[checkboxFieldPath]?.value as string }) - // The value of the field we're listening to for the slug - const targetFieldValue = useFormFields(([fields]) => { - return fields[fieldToUse]?.value as string - }) + const handleGenerate = useCallback( + (e: React.MouseEvent) => { + e.preventDefault() + + const targetFieldValue = getDataByPath(fieldToUse) as string - useEffect(() => { - if (checkboxValue) { if (targetFieldValue) { const formattedSlug = formatSlug(targetFieldValue) @@ -49,8 +46,9 @@ export const SlugComponent: React.FC = ({ } else { if (value !== '') setValue('') } - } - }, [targetFieldValue, checkboxValue, setValue, value]) + }, + [setValue, value, fieldToUse, getDataByPath], + ) const handleLock = useCallback( (e: React.MouseEvent) => { @@ -59,29 +57,30 @@ export const SlugComponent: React.FC = ({ dispatchFields({ type: 'UPDATE', path: checkboxFieldPath, - value: !checkboxValue, + value: !isLocked, }) }, - [checkboxValue, checkboxFieldPath, dispatchFields], + [isLocked, checkboxFieldPath, dispatchFields], ) - const readOnly = readOnlyFromProps || checkboxValue - return (
- + {!isLocked && ( + + )}
-
) diff --git a/templates/website/src/fields/slug/formatSlug.ts b/templates/website/src/fields/slug/formatSlug.ts index 9129de8932..0d4b78239b 100644 --- a/templates/website/src/fields/slug/formatSlug.ts +++ b/templates/website/src/fields/slug/formatSlug.ts @@ -1,8 +1,8 @@ import type { FieldHook } from 'payload' -export const formatSlug = (val: string): string => +export const formatSlug = (val: string): string | undefined => val - .replace(/ /g, '-') + ?.replace(/ /g, '-') .replace(/[^\w-]+/g, '') .toLowerCase() @@ -13,10 +13,10 @@ export const formatSlugHook = return formatSlug(value) } - if (operation === 'create' || !data?.slug) { - const fallbackData = data?.[fallback] || data?.[fallback] + if (operation === 'create' || data?.slug === undefined) { + const fallbackData = data?.[fallback] - if (fallbackData && typeof fallbackData === 'string') { + if (typeof fallbackData === 'string') { return formatSlug(fallbackData) } } diff --git a/templates/website/src/fields/slug/index.scss b/templates/website/src/fields/slug/index.scss index e3dd2d8369..514af3d225 100644 --- a/templates/website/src/fields/slug/index.scss +++ b/templates/website/src/fields/slug/index.scss @@ -3,6 +3,7 @@ display: flex; justify-content: space-between; align-items: center; + gap: calc(var(--base) / 2); } .lock-button { diff --git a/templates/with-vercel-website/src/fields/slug/SlugComponent.tsx b/templates/with-vercel-website/src/fields/slug/SlugComponent.tsx index f21ae829d2..8114973e16 100644 --- a/templates/with-vercel-website/src/fields/slug/SlugComponent.tsx +++ b/templates/with-vercel-website/src/fields/slug/SlugComponent.tsx @@ -1,5 +1,5 @@ 'use client' -import React, { useCallback, useEffect } from 'react' +import React, { useCallback } from 'react' import { TextFieldClientProps } from 'payload' import { useField, Button, TextInput, FieldLabel, useFormFields, useForm } from '@payloadcms/ui' @@ -27,21 +27,18 @@ export const SlugComponent: React.FC = ({ const { value, setValue } = useField({ path: path || field.name }) - const { dispatchFields } = useForm() + const { dispatchFields, getDataByPath } = useForm() - // The value of the checkbox - // We're using separate useFormFields to minimise re-renders - const checkboxValue = useFormFields(([fields]) => { + const isLocked = useFormFields(([fields]) => { return fields[checkboxFieldPath]?.value as string }) - // The value of the field we're listening to for the slug - const targetFieldValue = useFormFields(([fields]) => { - return fields[fieldToUse]?.value as string - }) + const handleGenerate = useCallback( + (e: React.MouseEvent) => { + e.preventDefault() + + const targetFieldValue = getDataByPath(fieldToUse) as string - useEffect(() => { - if (checkboxValue) { if (targetFieldValue) { const formattedSlug = formatSlug(targetFieldValue) @@ -49,8 +46,9 @@ export const SlugComponent: React.FC = ({ } else { if (value !== '') setValue('') } - } - }, [targetFieldValue, checkboxValue, setValue, value]) + }, + [setValue, value, fieldToUse, getDataByPath], + ) const handleLock = useCallback( (e: React.MouseEvent) => { @@ -59,29 +57,30 @@ export const SlugComponent: React.FC = ({ dispatchFields({ type: 'UPDATE', path: checkboxFieldPath, - value: !checkboxValue, + value: !isLocked, }) }, - [checkboxValue, checkboxFieldPath, dispatchFields], + [isLocked, checkboxFieldPath, dispatchFields], ) - const readOnly = readOnlyFromProps || checkboxValue - return (
- + {!isLocked && ( + + )}
-
) diff --git a/templates/with-vercel-website/src/fields/slug/formatSlug.ts b/templates/with-vercel-website/src/fields/slug/formatSlug.ts index 9129de8932..0d4b78239b 100644 --- a/templates/with-vercel-website/src/fields/slug/formatSlug.ts +++ b/templates/with-vercel-website/src/fields/slug/formatSlug.ts @@ -1,8 +1,8 @@ import type { FieldHook } from 'payload' -export const formatSlug = (val: string): string => +export const formatSlug = (val: string): string | undefined => val - .replace(/ /g, '-') + ?.replace(/ /g, '-') .replace(/[^\w-]+/g, '') .toLowerCase() @@ -13,10 +13,10 @@ export const formatSlugHook = return formatSlug(value) } - if (operation === 'create' || !data?.slug) { - const fallbackData = data?.[fallback] || data?.[fallback] + if (operation === 'create' || data?.slug === undefined) { + const fallbackData = data?.[fallback] - if (fallbackData && typeof fallbackData === 'string') { + if (typeof fallbackData === 'string') { return formatSlug(fallbackData) } } diff --git a/templates/with-vercel-website/src/fields/slug/index.scss b/templates/with-vercel-website/src/fields/slug/index.scss index e3dd2d8369..514af3d225 100644 --- a/templates/with-vercel-website/src/fields/slug/index.scss +++ b/templates/with-vercel-website/src/fields/slug/index.scss @@ -3,6 +3,7 @@ display: flex; justify-content: space-between; align-items: center; + gap: calc(var(--base) / 2); } .lock-button { From a374aabd8d5b52bd76d8aee1e713482b274f8262 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 12 Aug 2025 17:10:01 +0200 Subject: [PATCH 03/14] fix(ui): bulk upload issues (#13413) ### What? This PR contains a couple of fixes to the bulk upload process: - Credentials not being passed when fetching, leading to auth issues - Provide a fallback to crypto.randomUUID which is only available when using HTTPS or localhost ### Why? I use [separate admin and API URLs](#12682) and work off a remote dev server using custom hostnames. These issues may not impact the happy path of using localhost, but are dealbreakers in this environment. ### Fixes # _These are issues I found myself, I fixed them rather than raising issues for somebody else to pick up, but I can create issues to link to if required._ --- packages/ui/src/elements/BulkUpload/FormsManager/index.tsx | 3 ++- packages/ui/src/elements/BulkUpload/FormsManager/reducer.ts | 4 +++- packages/ui/src/elements/Table/OrderableTable.tsx | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/elements/BulkUpload/FormsManager/index.tsx b/packages/ui/src/elements/BulkUpload/FormsManager/index.tsx index c4f7074946..841adadf29 100644 --- a/packages/ui/src/elements/BulkUpload/FormsManager/index.tsx +++ b/packages/ui/src/elements/BulkUpload/FormsManager/index.tsx @@ -127,7 +127,7 @@ export function FormsManagerProvider({ children }: FormsManagerProps) { const initialStateRef = React.useRef(null) const getFormDataRef = React.useRef<() => Data>(() => ({})) - const actionURL = `${api}/${collectionSlug}` + const actionURL = `${serverURL}${api}/${collectionSlug}` const initializeSharedDocPermissions = React.useCallback(async () => { const params = { @@ -301,6 +301,7 @@ export function FormsManagerProvider({ children }: FormsManagerProps) { collectionSlug, getUploadHandler({ collectionSlug }), ), + credentials: 'include', method: 'POST', }) diff --git a/packages/ui/src/elements/BulkUpload/FormsManager/reducer.ts b/packages/ui/src/elements/BulkUpload/FormsManager/reducer.ts index 8abfb18aed..a87b1461d0 100644 --- a/packages/ui/src/elements/BulkUpload/FormsManager/reducer.ts +++ b/packages/ui/src/elements/BulkUpload/FormsManager/reducer.ts @@ -1,5 +1,7 @@ import type { FormState, UploadEdits } from 'payload' +import { v4 as uuidv4 } from 'uuid' + export type State = { activeIndex: number forms: { @@ -50,7 +52,7 @@ export function formsManagementReducer(state: State, action: Action): State { for (let i = 0; i < action.files.length; i++) { newForms[i] = { errorCount: 0, - formID: crypto.randomUUID(), + formID: crypto.randomUUID ? crypto.randomUUID() : uuidv4(), formState: { ...(action.initialState || {}), file: { diff --git a/packages/ui/src/elements/Table/OrderableTable.tsx b/packages/ui/src/elements/Table/OrderableTable.tsx index 38c0bb7e22..e812583165 100644 --- a/packages/ui/src/elements/Table/OrderableTable.tsx +++ b/packages/ui/src/elements/Table/OrderableTable.tsx @@ -119,8 +119,9 @@ export const OrderableTable: React.FC = ({ target, } - const response = await fetch(`${config.routes.api}/reorder`, { + const response = await fetch(`${config.serverURL}${config.routes.api}/reorder`, { body: JSON.stringify(jsonBody), + credentials: 'include', headers: { 'Content-Type': 'application/json', }, From 72f5763c2549964e1f40eb3c11a85af99e4fbcfd Mon Sep 17 00:00:00 2001 From: Marcus Michaels Date: Tue, 12 Aug 2025 16:15:10 +0100 Subject: [PATCH 04/14] fix(ui): field elements showing over the top of Preview content (#13148) --- packages/ui/src/elements/DocumentFields/index.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/src/elements/DocumentFields/index.scss b/packages/ui/src/elements/DocumentFields/index.scss index 48833e6e23..70191525e0 100644 --- a/packages/ui/src/elements/DocumentFields/index.scss +++ b/packages/ui/src/elements/DocumentFields/index.scss @@ -125,6 +125,7 @@ &--force-sidebar-wrap { display: block; + isolation: isolate; .document-fields { &__main { From 306b7f694388080a55cf4a242bb1c0d226f8d60c Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 12 Aug 2025 08:28:35 -0700 Subject: [PATCH 05/14] fix(ui): misalignment of nested toggles on document API tab (#13424) Fixes the weird misalignment of toggles in the API tab Before: image After: image --- .../next/src/views/API/RenderJSON/index.scss | 52 +++++++++++++++---- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/packages/next/src/views/API/RenderJSON/index.scss b/packages/next/src/views/API/RenderJSON/index.scss index dd98423e66..29fd5ef255 100644 --- a/packages/next/src/views/API/RenderJSON/index.scss +++ b/packages/next/src/views/API/RenderJSON/index.scss @@ -1,15 +1,17 @@ @import '~@payloadcms/ui/scss'; -$tab-width: 16px; +$tab-width: 24px; @layer payload-default { .query-inspector { + --tab-width: 24px; + &__json-children { position: relative; &--nested { & li { - padding-left: $tab-width; + padding-left: 8px; } } @@ -23,6 +25,14 @@ $tab-width: 16px; } } + &__row-line { + &--nested { + .query-inspector__json-children { + padding-left: var(--tab-width); + } + } + } + &__list-wrap { position: relative; } @@ -37,10 +47,16 @@ $tab-width: 16px; border-bottom-right-radius: 0; position: relative; display: flex; - gap: 10px; + column-gap: 14px; + row-gap: 10px; align-items: center; - left: -3px; + left: 0; width: calc(100% + 3px); + background-color: var(--theme-elevation-50); + + &:not(.query-inspector__list-toggle--empty) { + margin-left: calc(var(--tab-width) * -1 - 10px); + } svg .stroke { stroke: var(--theme-elevation-400); @@ -82,14 +98,32 @@ $tab-width: 16px; &__bracket { position: relative; - &--nested { - margin-left: $tab-width; - } - &--position-end { - left: 1px; + left: 2px; width: calc(100% - 5px); } } + + // Some specific rules targetting the very top of the nested JSON structure or very first items since they need slightly different styling + &__results { + & > .query-inspector__row-line--nested { + & > .query-inspector__list-toggle { + margin-left: 0; + column-gap: 6px; + + .query-inspector__toggle-row-icon { + margin-left: -4px; + } + } + + & > .query-inspector__json-children { + padding-left: calc(var(--base) * 1); + } + + & > .query-inspector__bracket--nested > .query-inspector__bracket--position-end { + padding-left: 16px; + } + } + } } } From 995f96bc701bfdef0558cfbe00c628e08a43ccf5 Mon Sep 17 00:00:00 2001 From: Jarrod Flesch <30633324+JarrodMFlesch@users.noreply.github.com> Date: Tue, 12 Aug 2025 11:33:29 -0400 Subject: [PATCH 06/14] feat(plugin-multi-tenant): allow tenant field overrides (#13316) Allows user to override more of the tenant field config. Now you can override most of the field config with: ### At the root level ```ts /** * Field configuration for the field added to all tenant enabled collections */ tenantField?: RootTenantFieldConfigOverrides ``` ### At the collection level Setting collection level overrides will replace the root level overrides shown above. ```ts collections: { [key in CollectionSlug]?: { // ... rest of the types /** * Overrides for the tenant field, will override the entire tenantField configuration */ tenantFieldOverrides?: CollectionTenantFieldConfigOverrides } } ``` --- docs/plugins/multi-tenant.mdx | 103 ++++++++++------- .../src/components/TenantSelector/index.tsx | 12 +- .../plugin-multi-tenant/src/exports/fields.ts | 1 - .../src/fields/tenantField/index.ts | 104 ++++++++++-------- packages/plugin-multi-tenant/src/index.ts | 78 +++++-------- .../src/translations/languages/ar.ts | 7 +- .../src/translations/languages/az.ts | 9 +- .../src/translations/languages/bg.ts | 7 +- .../src/translations/languages/bnBd.ts | 16 +++ .../src/translations/languages/bnIn.ts | 16 +++ .../src/translations/languages/ca.ts | 9 +- .../src/translations/languages/cs.ts | 7 +- .../src/translations/languages/da.ts | 9 +- .../src/translations/languages/de.ts | 9 +- .../src/translations/languages/en.ts | 7 +- .../src/translations/languages/es.ts | 7 +- .../src/translations/languages/et.ts | 9 +- .../src/translations/languages/fa.ts | 9 +- .../src/translations/languages/fr.ts | 7 +- .../src/translations/languages/he.ts | 7 +- .../src/translations/languages/hr.ts | 9 +- .../src/translations/languages/hu.ts | 9 +- .../src/translations/languages/hy.ts | 9 +- .../src/translations/languages/id.ts | 16 +++ .../src/translations/languages/it.ts | 9 +- .../src/translations/languages/ja.ts | 9 +- .../src/translations/languages/ko.ts | 9 +- .../src/translations/languages/lt.ts | 9 +- .../src/translations/languages/lv.ts | 7 +- .../src/translations/languages/my.ts | 9 +- .../src/translations/languages/nb.ts | 7 +- .../src/translations/languages/nl.ts | 9 +- .../src/translations/languages/pl.ts | 7 +- .../src/translations/languages/pt.ts | 9 +- .../src/translations/languages/ro.ts | 9 +- .../src/translations/languages/rs.ts | 9 +- .../src/translations/languages/rsLatin.ts | 7 +- .../src/translations/languages/ru.ts | 7 +- .../src/translations/languages/sk.ts | 7 +- .../src/translations/languages/sl.ts | 9 +- .../src/translations/languages/sv.ts | 9 +- .../src/translations/languages/th.ts | 9 +- .../src/translations/languages/tr.ts | 9 +- .../src/translations/languages/uk.ts | 9 +- .../src/translations/languages/vi.ts | 9 +- .../src/translations/languages/zh.ts | 8 +- .../src/translations/languages/zhTw.ts | 9 +- .../src/translations/types.ts | 7 +- packages/plugin-multi-tenant/src/types.ts | 77 +++++++++++-- .../slateToLexical/converter/index.ts | 2 +- packages/translations/src/languages/zhTw.ts | 15 +-- test/plugin-multi-tenant/config.ts | 10 +- 52 files changed, 478 insertions(+), 303 deletions(-) create mode 100644 packages/plugin-multi-tenant/src/translations/languages/bnBd.ts create mode 100644 packages/plugin-multi-tenant/src/translations/languages/bnIn.ts create mode 100644 packages/plugin-multi-tenant/src/translations/languages/id.ts diff --git a/docs/plugins/multi-tenant.mdx b/docs/plugins/multi-tenant.mdx index 406e20a2f5..e99740454e 100644 --- a/docs/plugins/multi-tenant.mdx +++ b/docs/plugins/multi-tenant.mdx @@ -54,8 +54,15 @@ The plugin accepts an object with the following properties: ```ts type MultiTenantPluginConfig = { /** - * After a tenant is deleted, the plugin will attempt - * to clean up related documents + * Base path for your application + * + * https://nextjs.org/docs/app/api-reference/config/next-config-js/basePath + * + * @default undefined + */ + basePath?: string + /** + * After a tenant is deleted, the plugin will attempt to clean up related documents * - removing documents with the tenant ID * - removing the tenant from users * @@ -68,15 +75,18 @@ type MultiTenantPluginConfig = { collections: { [key in CollectionSlug]?: { /** - * Set to `true` if you want the collection to - * behave as a global + * Set to `true` if you want the collection to behave as a global * * @default false */ isGlobal?: boolean /** - * Set to `false` if you want to manually apply - * the baseFilter + * Overrides for the tenant field, will override the entire tenantField configuration + */ + tenantFieldOverrides?: CollectionTenantFieldConfigOverrides + /** + * Set to `false` if you want to manually apply the baseListFilter + * Set to `false` if you want to manually apply the baseFilter * * @default true */ @@ -94,8 +104,7 @@ type MultiTenantPluginConfig = { */ useBaseListFilter?: boolean /** - * Set to `false` if you want to handle collection access - * manually without the multi-tenant constraints applied + * Set to `false` if you want to handle collection access manually without the multi-tenant constraints applied * * @default true */ @@ -104,8 +113,7 @@ type MultiTenantPluginConfig = { } /** * Enables debug mode - * - Makes the tenant field visible in the - * admin UI within applicable collections + * - Makes the tenant field visible in the admin UI within applicable collections * * @default false */ @@ -117,27 +125,41 @@ type MultiTenantPluginConfig = { */ enabled?: boolean /** - * Field configuration for the field added - * to all tenant enabled collections + * Localization for the plugin */ - tenantField?: { - access?: RelationshipField['access'] - /** - * The name of the field added to all tenant - * enabled collections - * - * @default 'tenant' - */ - name?: string + i18n?: { + translations: { + [key in AcceptedLanguages]?: { + /** + * @default 'You are about to change ownership from <0>{{fromTenant}} to <0>{{toTenant}}' + */ + 'confirm-modal-tenant-switch--body'?: string + /** + * `tenantLabel` defaults to the value of the `nav-tenantSelector-label` translation + * + * @default 'Confirm {{tenantLabel}} change' + */ + 'confirm-modal-tenant-switch--heading'?: string + /** + * @default 'Assigned Tenant' + */ + 'field-assignedTenant-label'?: string + /** + * @default 'Tenant' + */ + 'nav-tenantSelector-label'?: string + } + } } /** - * Field configuration for the field added - * to the users collection + * Field configuration for the field added to all tenant enabled collections + */ + tenantField?: RootTenantFieldConfigOverrides + /** + * Field configuration for the field added to the users collection * - * If `includeDefaultField` is `false`, you must - * include the field on your users collection manually - * This is useful if you want to customize the field - * or place the field in a specific location + * If `includeDefaultField` is `false`, you must include the field on your users collection manually + * This is useful if you want to customize the field or place the field in a specific location */ tenantsArrayField?: | { @@ -158,8 +180,7 @@ type MultiTenantPluginConfig = { */ arrayTenantFieldName?: string /** - * When `includeDefaultField` is `true`, the field will - * be added to the users collection automatically + * When `includeDefaultField` is `true`, the field will be added to the users collection automatically */ includeDefaultField?: true /** @@ -176,8 +197,7 @@ type MultiTenantPluginConfig = { arrayFieldName?: string arrayTenantFieldName?: string /** - * When `includeDefaultField` is `false`, you must - * include the field on your users collection manually + * When `includeDefaultField` is `false`, you must include the field on your users collection manually */ includeDefaultField?: false rowFields?: never @@ -186,8 +206,9 @@ type MultiTenantPluginConfig = { /** * Customize tenant selector label * - * Either a string or an object where the keys are i18n - * codes and the values are the string labels + * Either a string or an object where the keys are i18n codes and the values are the string labels + * + * @deprecated Use `i18n.translations` instead. */ tenantSelectorLabel?: | Partial<{ @@ -201,27 +222,25 @@ type MultiTenantPluginConfig = { */ tenantsSlug?: string /** - * Function that determines if a user has access - * to _all_ tenants + * Function that determines if a user has access to _all_ tenants * * Useful for super-admin type users */ userHasAccessToAllTenants?: ( - user: ConfigTypes extends { user: unknown } ? ConfigTypes['user'] : User, + user: ConfigTypes extends { user: unknown } + ? ConfigTypes['user'] + : TypedUser, ) => boolean /** - * Opt out of adding access constraints to - * the tenants collection + * Opt out of adding access constraints to the tenants collection */ useTenantsCollectionAccess?: boolean /** - * Opt out including the baseFilter to filter - * tenants by selected tenant + * Opt out including the baseListFilter to filter tenants by selected tenant */ useTenantsListFilter?: boolean /** - * Opt out including the baseFilter to filter - * users by selected tenant + * Opt out including the baseListFilter to filter users by selected tenant */ useUsersTenantFilter?: boolean } diff --git a/packages/plugin-multi-tenant/src/components/TenantSelector/index.tsx b/packages/plugin-multi-tenant/src/components/TenantSelector/index.tsx index ae9b26929d..f0d1a050b8 100644 --- a/packages/plugin-multi-tenant/src/components/TenantSelector/index.tsx +++ b/packages/plugin-multi-tenant/src/components/TenantSelector/index.tsx @@ -92,7 +92,9 @@ export const TenantSelector = ({ label, viewType }: { label: string; viewType?:
} - heading={t('plugin-multi-tenant:confirm-tenant-switch--heading', { - tenantLabel: getTranslation(label, i18n), + heading={t('plugin-multi-tenant:confirm-modal-tenant-switch--heading', { + tenantLabel: label + ? getTranslation(label, i18n) + : t('plugin-multi-tenant:nav-tenantSelector-label'), })} modalSlug={confirmSwitchTenantSlug} onConfirm={() => { diff --git a/packages/plugin-multi-tenant/src/exports/fields.ts b/packages/plugin-multi-tenant/src/exports/fields.ts index 7dd1ac4477..b333696f27 100644 --- a/packages/plugin-multi-tenant/src/exports/fields.ts +++ b/packages/plugin-multi-tenant/src/exports/fields.ts @@ -1,2 +1 @@ -export { tenantField } from '../fields/tenantField/index.js' export { tenantsArrayField } from '../fields/tenantsArrayField/index.js' diff --git a/packages/plugin-multi-tenant/src/fields/tenantField/index.ts b/packages/plugin-multi-tenant/src/fields/tenantField/index.ts index a8b80564eb..643b8d16f2 100644 --- a/packages/plugin-multi-tenant/src/fields/tenantField/index.ts +++ b/packages/plugin-multi-tenant/src/fields/tenantField/index.ts @@ -1,65 +1,83 @@ -import { type RelationshipField } from 'payload' +import type { RelationshipFieldSingleValidation, SingleRelationshipField } from 'payload' + import { APIError } from 'payload' +import type { RootTenantFieldConfigOverrides } from '../../types.js' + import { defaults } from '../../defaults.js' import { getCollectionIDType } from '../../utilities/getCollectionIDType.js' import { getTenantFromCookie } from '../../utilities/getTenantFromCookie.js' type Args = { - access?: RelationshipField['access'] debug?: boolean name: string + overrides?: RootTenantFieldConfigOverrides tenantsCollectionSlug: string unique: boolean } export const tenantField = ({ name = defaults.tenantFieldName, - access = undefined, debug, + overrides: _overrides = {}, tenantsCollectionSlug = defaults.tenantCollectionSlug, unique, -}: Args): RelationshipField => ({ - name, - type: 'relationship', - access, - admin: { - allowCreate: false, - allowEdit: false, - components: { - Field: { - clientProps: { - debug, - unique, +}: Args): SingleRelationshipField => { + const { validate, ...overrides } = _overrides || {} + return { + ...(overrides || {}), + name, + type: 'relationship', + access: overrides?.access || {}, + admin: { + allowCreate: false, + allowEdit: false, + disableListColumn: true, + disableListFilter: true, + ...(overrides?.admin || {}), + components: { + ...(overrides?.admin?.components || {}), + Field: { + path: '@payloadcms/plugin-multi-tenant/client#TenantField', + ...(typeof overrides?.admin?.components?.Field !== 'string' + ? overrides?.admin?.components?.Field || {} + : {}), + clientProps: { + ...(typeof overrides?.admin?.components?.Field !== 'string' + ? (overrides?.admin?.components?.Field || {})?.clientProps + : {}), + debug, + unique, + }, }, - path: '@payloadcms/plugin-multi-tenant/client#TenantField', }, }, - disableListColumn: true, - disableListFilter: true, - }, - hasMany: false, - hooks: { - beforeChange: [ - ({ req, value }) => { - const idType = getCollectionIDType({ - collectionSlug: tenantsCollectionSlug, - payload: req.payload, - }) - if (!value) { - const tenantFromCookie = getTenantFromCookie(req.headers, idType) - if (tenantFromCookie) { - return tenantFromCookie + hasMany: false, + hooks: { + ...(overrides.hooks || []), + beforeChange: [ + ({ req, value }) => { + const idType = getCollectionIDType({ + collectionSlug: tenantsCollectionSlug, + payload: req.payload, + }) + if (!value) { + const tenantFromCookie = getTenantFromCookie(req.headers, idType) + if (tenantFromCookie) { + return tenantFromCookie + } + throw new APIError('You must select a tenant', 400, null, true) } - throw new APIError('You must select a tenant', 400, null, true) - } - return idType === 'number' ? parseFloat(value) : value - }, - ], - }, - index: true, - // @ts-expect-error translations are not typed for this plugin - label: ({ t }) => t('plugin-multi-tenant:field-assignedTentant-label'), - relationTo: tenantsCollectionSlug, - unique, -}) + return idType === 'number' ? parseFloat(value) : value + }, + ...(overrides?.hooks?.beforeChange || []), + ], + }, + index: true, + validate: (validate as RelationshipFieldSingleValidation) || undefined, + // @ts-expect-error translations are not typed for this plugin + label: overrides?.label || (({ t }) => t('plugin-multi-tenant:field-assignedTenant-label')), + relationTo: tenantsCollectionSlug, + unique, + } +} diff --git a/packages/plugin-multi-tenant/src/index.ts b/packages/plugin-multi-tenant/src/index.ts index 88383982ba..fe8a71560b 100644 --- a/packages/plugin-multi-tenant/src/index.ts +++ b/packages/plugin-multi-tenant/src/index.ts @@ -40,7 +40,6 @@ export const multiTenantPlugin = pluginConfig?.tenantsArrayField?.arrayFieldName || defaults.tenantsArrayFieldName const tenantsArrayTenantFieldName = pluginConfig?.tenantsArrayField?.arrayTenantFieldName || defaults.tenantsArrayTenantFieldName - const tenantSelectorLabel = pluginConfig.tenantSelectorLabel || defaults.tenantSelectorLabel const basePath = pluginConfig.basePath || defaults.basePath /** @@ -69,37 +68,6 @@ export const multiTenantPlugin = incomingConfig.collections = [] } - /** - * Add tenant selector localized labels - */ - if (typeof tenantSelectorLabel === 'object') { - if (!incomingConfig.i18n) { - incomingConfig.i18n = {} - } - Object.entries(tenantSelectorLabel).forEach(([_locale, label]) => { - const locale = _locale as AcceptedLanguages - if (!incomingConfig.i18n) { - incomingConfig.i18n = {} - } - if (!incomingConfig.i18n.translations) { - incomingConfig.i18n.translations = {} - } - if (!(locale in incomingConfig.i18n.translations)) { - incomingConfig.i18n.translations[locale] = {} - } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - if (!('multiTenant' in incomingConfig.i18n.translations[locale])) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - incomingConfig.i18n.translations[locale].multiTenant = {} - } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - incomingConfig.i18n.translations[locale].multiTenant.selectorLabel = label - }) - } - /** * Add tenants array field to users collection */ @@ -284,6 +252,10 @@ export const multiTenantPlugin = tenantsCollectionSlug, }) + const overrides = pluginConfig.collections[collection.slug]?.tenantFieldOverrides + ? pluginConfig.collections[collection.slug]?.tenantFieldOverrides + : pluginConfig.tenantField || {} + /** * Add tenant field to enabled collections */ @@ -291,9 +263,9 @@ export const multiTenantPlugin = 0, 0, tenantField({ - ...(pluginConfig?.tenantField || {}), name: tenantFieldName, debug: pluginConfig.debug, + overrides, tenantsCollectionSlug, unique: isGlobal, }), @@ -382,7 +354,7 @@ export const multiTenantPlugin = */ incomingConfig.admin.components.beforeNavLinks.push({ clientProps: { - label: tenantSelectorLabel, + label: pluginConfig.tenantSelectorLabel || undefined, }, path: '@payloadcms/plugin-multi-tenant/client#TenantSelector', }) @@ -390,22 +362,30 @@ export const multiTenantPlugin = /** * Merge plugin translations */ - - const simplifiedTranslations = Object.entries(translations).reduce( - (acc, [key, value]) => { - acc[key] = value.translations - return acc - }, - {} as Record, - ) - - incomingConfig.i18n = { - ...incomingConfig.i18n, - translations: deepMergeSimple( - simplifiedTranslations, - incomingConfig.i18n?.translations ?? {}, - ), + if (!incomingConfig.i18n) { + incomingConfig.i18n = {} } + Object.entries(translations).forEach(([locale, pluginI18nObject]) => { + const typedLocale = locale as AcceptedLanguages + if (!incomingConfig.i18n!.translations) { + incomingConfig.i18n!.translations = {} + } + if (!(typedLocale in incomingConfig.i18n!.translations)) { + incomingConfig.i18n!.translations[typedLocale] = {} + } + if (!('plugin-multi-tenant' in incomingConfig.i18n!.translations[typedLocale]!)) { + ;(incomingConfig.i18n!.translations[typedLocale] as PluginDefaultTranslationsObject)[ + 'plugin-multi-tenant' + ] = {} as PluginDefaultTranslationsObject['plugin-multi-tenant'] + } + + ;(incomingConfig.i18n!.translations[typedLocale] as PluginDefaultTranslationsObject)[ + 'plugin-multi-tenant' + ] = { + ...pluginI18nObject.translations['plugin-multi-tenant'], + ...(pluginConfig.i18n?.translations?.[typedLocale] || {}), + } + }) return incomingConfig } diff --git a/packages/plugin-multi-tenant/src/translations/languages/ar.ts b/packages/plugin-multi-tenant/src/translations/languages/ar.ts index 74f2e3cf26..9d6fa76eeb 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/ar.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/ar.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const arTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': + 'confirm-modal-tenant-switch--body': 'أنت على وشك تغيير الملكية من <0>{{fromTenant}} إلى <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'تأكيد تغيير {{tenantLabel}}', - 'field-assignedTentant-label': 'المستأجر المعين', + 'confirm-modal-tenant-switch--heading': 'تأكيد تغيير {{tenantLabel}}', + 'field-assignedTenant-label': 'المستأجر المعين', + 'nav-tenantSelector-label': 'المستأجر', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/az.ts b/packages/plugin-multi-tenant/src/translations/languages/az.ts index 0f122d5dcb..39969c2a3f 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/az.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/az.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const azTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'Siz <0>{{fromTenant}} mülkiyyətini <0>{{toTenant}} mülkiyyətinə dəyişdirəcəksiniz.', - 'confirm-tenant-switch--heading': '{{tenantLabel}} dəyişikliyini təsdiqləyin', - 'field-assignedTentant-label': 'Təyin edilmiş İcarəçi', + 'confirm-modal-tenant-switch--body': + 'Siz <0>{{fromTenant}}-dən <0>{{toTenant}}-a mülkiyyəti dəyişməyə hazırlaşırsınız', + 'confirm-modal-tenant-switch--heading': '{{tenantLabel}} dəyişikliyini təsdiqləyin', + 'field-assignedTenant-label': 'Təyin edilmiş İcarəçi', + 'nav-tenantSelector-label': 'Kirayəçi', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/bg.ts b/packages/plugin-multi-tenant/src/translations/languages/bg.ts index e37f81c869..a8db574394 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/bg.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/bg.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const bgTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': + 'confirm-modal-tenant-switch--body': 'Предстои да промените собствеността от <0>{{fromTenant}} на <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Потвърдете промяната на {{tenantLabel}}', - 'field-assignedTentant-label': 'Назначен наемател', + 'confirm-modal-tenant-switch--heading': 'Потвърждаване на промяна в {{tenantLabel}}', + 'field-assignedTenant-label': 'Назначен наемател', + 'nav-tenantSelector-label': 'Потребител', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/bnBd.ts b/packages/plugin-multi-tenant/src/translations/languages/bnBd.ts new file mode 100644 index 0000000000..b1389b170d --- /dev/null +++ b/packages/plugin-multi-tenant/src/translations/languages/bnBd.ts @@ -0,0 +1,16 @@ +import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js' + +export const bnBdTranslations: PluginDefaultTranslationsObject = { + 'plugin-multi-tenant': { + 'confirm-modal-tenant-switch--body': + 'আপনি <0>{{fromTenant}} থেকে <0>{{toTenant}} তে মালিকানা পরিবর্তন করতে চলেছেন।', + 'confirm-modal-tenant-switch--heading': '{{tenantLabel}} পরিবর্তন নিশ্চিত করুন', + 'field-assignedTenant-label': 'নির্ধারিত টেনেন্ট', + 'nav-tenantSelector-label': 'ভাড়াটিয়া', + }, +} + +export const bnBd: PluginLanguage = { + dateFNSKey: 'bn-BD', + translations: bnBdTranslations, +} diff --git a/packages/plugin-multi-tenant/src/translations/languages/bnIn.ts b/packages/plugin-multi-tenant/src/translations/languages/bnIn.ts new file mode 100644 index 0000000000..bf43d4d9ea --- /dev/null +++ b/packages/plugin-multi-tenant/src/translations/languages/bnIn.ts @@ -0,0 +1,16 @@ +import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js' + +export const bnInTranslations: PluginDefaultTranslationsObject = { + 'plugin-multi-tenant': { + 'confirm-modal-tenant-switch--body': + 'আপনি স্বত্বাধিকার পরিবর্তন করতে চলেছেন <0>{{fromTenant}} থেকে <0>{{toTenant}} এ।', + 'confirm-modal-tenant-switch--heading': '{{tenantLabel}} পরিবর্তন নিশ্চিত করুন', + 'field-assignedTenant-label': 'নির্ধারিত টেনেন্ট', + 'nav-tenantSelector-label': 'ভাড়াটিয়া', + }, +} + +export const bnIn: PluginLanguage = { + dateFNSKey: 'bn-IN', + translations: bnInTranslations, +} diff --git a/packages/plugin-multi-tenant/src/translations/languages/ca.ts b/packages/plugin-multi-tenant/src/translations/languages/ca.ts index 01d1135ecb..a95e1e4fc8 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/ca.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/ca.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const caTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'Estàs a punt de canviar la propietat de <0>{{fromTenant}} a <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Confirmeu el canvi de {{tenantLabel}}', - 'field-assignedTentant-label': 'Llogater Assignat', + 'confirm-modal-tenant-switch--body': + 'Està a punt de canviar la propietat de <0>{{fromTenant}} a <0>{{toTenant}}', + 'confirm-modal-tenant-switch--heading': 'Confirmeu el canvi de {{tenantLabel}}', + 'field-assignedTenant-label': 'Llogater Assignat', + 'nav-tenantSelector-label': 'Inquilí', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/cs.ts b/packages/plugin-multi-tenant/src/translations/languages/cs.ts index ba184d63bc..29ec6f4e4b 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/cs.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/cs.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const csTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': + 'confirm-modal-tenant-switch--body': 'Chystáte se změnit vlastnictví z <0>{{fromTenant}} na <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Potvrďte změnu {{tenantLabel}}', - 'field-assignedTentant-label': 'Přiřazený nájemce', + 'confirm-modal-tenant-switch--heading': 'Potvrďte změnu {{tenantLabel}}', + 'field-assignedTenant-label': 'Přiřazený nájemce', + 'nav-tenantSelector-label': 'Nájemce', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/da.ts b/packages/plugin-multi-tenant/src/translations/languages/da.ts index e6e32fd7fc..4b51069e7a 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/da.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/da.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const daTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'Du er ved at ændre ejerskab fra <0>{{fromTenant}} til <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Bekræft {{tenantLabel}} ændring', - 'field-assignedTentant-label': 'Tildelt Lejer', + 'confirm-modal-tenant-switch--body': + 'Du er ved at skifte ejerskab fra <0>{{fromTenant}} til <0>{{toTenant}}', + 'confirm-modal-tenant-switch--heading': 'Bekræft ændring af {{tenantLabel}}', + 'field-assignedTenant-label': 'Tildelt Lejer', + 'nav-tenantSelector-label': 'Lejer', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/de.ts b/packages/plugin-multi-tenant/src/translations/languages/de.ts index f709cef41b..29300a2741 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/de.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/de.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const deTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'Sie sind dabei, den Besitz von <0>{{fromTenant}} auf <0>{{toTenant}} zu übertragen.', - 'confirm-tenant-switch--heading': 'Bestätigen Sie die Änderung von {{tenantLabel}}.', - 'field-assignedTentant-label': 'Zugewiesener Mandant', + 'confirm-modal-tenant-switch--body': + 'Sie sind dabei, den Besitz von <0>{{fromTenant}} zu <0>{{toTenant}} zu ändern.', + 'confirm-modal-tenant-switch--heading': 'Bestätigung der Änderung von {{tenantLabel}}', + 'field-assignedTenant-label': 'Zugewiesener Mandant', + 'nav-tenantSelector-label': 'Mieter', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/en.ts b/packages/plugin-multi-tenant/src/translations/languages/en.ts index 35391929a8..7b41128e44 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/en.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/en.ts @@ -2,10 +2,11 @@ import type { PluginLanguage } from '../types.js' export const enTranslations = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': + 'confirm-modal-tenant-switch--body': 'You are about to change ownership from <0>{{fromTenant}} to <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Confirm {{tenantLabel}} change', - 'field-assignedTentant-label': 'Assigned Tenant', + 'confirm-modal-tenant-switch--heading': 'Confirm {{tenantLabel}} change', + 'field-assignedTenant-label': 'Assigned Tenant', + 'nav-tenantSelector-label': 'Tenant', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/es.ts b/packages/plugin-multi-tenant/src/translations/languages/es.ts index cacf035d43..e9d33e9766 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/es.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/es.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const esTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': + 'confirm-modal-tenant-switch--body': 'Está a punto de cambiar la propiedad de <0>{{fromTenant}} a <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Confirme el cambio de {{tenantLabel}}', - 'field-assignedTentant-label': 'Inquilino Asignado', + 'confirm-modal-tenant-switch--heading': 'Confirme el cambio de {{tenantLabel}}', + 'field-assignedTenant-label': 'Inquilino Asignado', + 'nav-tenantSelector-label': 'Inquilino', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/et.ts b/packages/plugin-multi-tenant/src/translations/languages/et.ts index 71dfa3ade7..a95155807e 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/et.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/et.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const etTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'Te olete tegemas omandiõiguse muudatust <0>{{fromTenant}}lt <0>{{toTenant}}le.', - 'confirm-tenant-switch--heading': 'Kinnita {{tenantLabel}} muutus', - 'field-assignedTentant-label': 'Määratud üürnik', + 'confirm-modal-tenant-switch--body': + 'Te olete just muutmas omandiõigust <0>{{fromTenant}} -lt <0>{{toTenant}} -le.', + 'confirm-modal-tenant-switch--heading': 'Kinnita {{tenantLabel}} muutus', + 'field-assignedTenant-label': 'Määratud üürnik', + 'nav-tenantSelector-label': 'Üürnik', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/fa.ts b/packages/plugin-multi-tenant/src/translations/languages/fa.ts index d719529be8..99d5f8c54a 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/fa.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/fa.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const faTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'شما در حال تغییر مالکیت از <0>{{fromTenant}} به <0>{{toTenant}} هستید', - 'confirm-tenant-switch--heading': 'تایید تغییر {{tenantLabel}}', - 'field-assignedTentant-label': 'مستاجر اختصاص یافته', + 'confirm-modal-tenant-switch--body': + 'شما در حال تغییر مالکیت از <0>{{fromTenant}} به <0>{{toTenant}} هستید.', + 'confirm-modal-tenant-switch--heading': 'تأیید تغییر {{tenantLabel}}', + 'field-assignedTenant-label': 'مستاجر اختصاص یافته', + 'nav-tenantSelector-label': 'مستاجر', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/fr.ts b/packages/plugin-multi-tenant/src/translations/languages/fr.ts index 542505c01f..ac692ced09 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/fr.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/fr.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const frTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': + 'confirm-modal-tenant-switch--body': 'Vous êtes sur le point de changer la propriété de <0>{{fromTenant}} à <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Confirmer le changement de {{tenantLabel}}', - 'field-assignedTentant-label': 'Locataire Attribué', + 'confirm-modal-tenant-switch--heading': 'Confirmer le changement de {{tenantLabel}}', + 'field-assignedTenant-label': 'Locataire Attribué', + 'nav-tenantSelector-label': 'Locataire', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/he.ts b/packages/plugin-multi-tenant/src/translations/languages/he.ts index 008204fa24..42a21fd626 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/he.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/he.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const heTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': + 'confirm-modal-tenant-switch--body': 'אתה עומד לשנות בעלות מ- <0>{{fromTenant}} ל- <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'אשר שינוי {{tenantLabel}}', - 'field-assignedTentant-label': 'דייר מוקצה', + 'confirm-modal-tenant-switch--heading': 'אשר שינוי {{tenantLabel}}', + 'field-assignedTenant-label': 'דייר מוקצה', + 'nav-tenantSelector-label': 'דייר', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/hr.ts b/packages/plugin-multi-tenant/src/translations/languages/hr.ts index af4f9ecd52..acf94bc57d 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/hr.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/hr.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const hrTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'Upravo ćete promijeniti vlasništvo sa <0>{{fromTenant}} na <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Potvrdi promjenu {{tenantLabel}}', - 'field-assignedTentant-label': 'Dodijeljeni stanar', + 'confirm-modal-tenant-switch--body': + 'Na rubu ste promjene vlasništva iz <0>{{fromTenant}} u <0>{{toTenant}}', + 'confirm-modal-tenant-switch--heading': 'Potvrdite promjenu {{tenantLabel}}', + 'field-assignedTenant-label': 'Dodijeljeni stanar', + 'nav-tenantSelector-label': 'Podstanar', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/hu.ts b/packages/plugin-multi-tenant/src/translations/languages/hu.ts index dbf8174ba7..4eed8bdcb3 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/hu.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/hu.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const huTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'Ön azon van, hogy megváltoztassa a tulajdonjogot <0>{{fromTenant}}-ről <0>{{toTenant}}-re.', - 'confirm-tenant-switch--heading': 'Erősítse meg a(z) {{tenantLabel}} változtatást', - 'field-assignedTentant-label': 'Kijelölt Bérlő', + 'confirm-modal-tenant-switch--body': + 'Közel áll ahhoz, hogy megváltoztassa a tulajdonságot <0>{{fromTenant}} -ból <0>{{toTenant}} -ba.', + 'confirm-modal-tenant-switch--heading': 'Erősítse meg a {{tenantLabel}} változást', + 'field-assignedTenant-label': 'Kijelölt Bérlő', + 'nav-tenantSelector-label': 'Bérlő', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/hy.ts b/packages/plugin-multi-tenant/src/translations/languages/hy.ts index 10911dcd22..e1edf58f1c 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/hy.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/hy.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const hyTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'Դուք պատրաստ եք փոխել գերեցդիմատնին ընկերությունը <0>{{fromTenant}}-ից <0>{{toTenant}}-ին', - 'confirm-tenant-switch--heading': 'Հաստատեք {{tenantLabel}} փոփոխությունը', - 'field-assignedTentant-label': 'Հանձնարարված վարձակալ', + 'confirm-modal-tenant-switch--body': + 'Դուք պատրաստվում եք փոխել սեփականությունը <0>{{fromTenant}}-ից <0>{{toTenant}}-ին:', + 'confirm-modal-tenant-switch--heading': 'Հաստատեք {{tenantLabel}}֊ի փոփոխությունը', + 'field-assignedTenant-label': 'Հանձնարարված վարձակալ', + 'nav-tenantSelector-label': 'Տենանտ', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/id.ts b/packages/plugin-multi-tenant/src/translations/languages/id.ts new file mode 100644 index 0000000000..3902fd8dfc --- /dev/null +++ b/packages/plugin-multi-tenant/src/translations/languages/id.ts @@ -0,0 +1,16 @@ +import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js' + +export const idTranslations: PluginDefaultTranslationsObject = { + 'plugin-multi-tenant': { + 'confirm-modal-tenant-switch--body': + 'Anda akan mengubah kepemilikan dari <0>{{fromTenant}} ke <0>{{toTenant}}', + 'confirm-modal-tenant-switch--heading': 'Konfirmasi perubahan {{tenantLabel}}', + 'field-assignedTenant-label': 'Penyewa yang Ditugaskan', + 'nav-tenantSelector-label': 'Penyewa', + }, +} + +export const id: PluginLanguage = { + dateFNSKey: 'id', + translations: idTranslations, +} diff --git a/packages/plugin-multi-tenant/src/translations/languages/it.ts b/packages/plugin-multi-tenant/src/translations/languages/it.ts index 86bc1ca184..31cca2a856 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/it.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/it.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const itTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'Stai per cambiare proprietà da <0>{{fromTenant}} a <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Conferma il cambiamento di {{tenantLabel}}', - 'field-assignedTentant-label': 'Inquilino Assegnato', + 'confirm-modal-tenant-switch--body': + 'Stai per cambiare il possesso da <0>{{fromTenant}} a <0>{{toTenant}}', + 'confirm-modal-tenant-switch--heading': 'Conferma il cambiamento di {{tenantLabel}}', + 'field-assignedTenant-label': 'Inquilino Assegnato', + 'nav-tenantSelector-label': 'Inquilino', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/ja.ts b/packages/plugin-multi-tenant/src/translations/languages/ja.ts index 6e9a818036..972a41ed8b 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/ja.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/ja.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const jaTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'あなたは所有権を<0>{{fromTenant}}から<0>{{toTenant}}へ変更しようとしています', - 'confirm-tenant-switch--heading': '{{tenantLabel}}の変更を確認してください', - 'field-assignedTentant-label': '割り当てられたテナント', + 'confirm-modal-tenant-switch--body': + 'あなたは、<0>{{fromTenant}}から<0>{{toTenant}}への所有権を変更しようとしています。', + 'confirm-modal-tenant-switch--heading': '{{tenantLabel}}の変更を確認します', + 'field-assignedTenant-label': '割り当てられたテナント', + 'nav-tenantSelector-label': 'テナント', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/ko.ts b/packages/plugin-multi-tenant/src/translations/languages/ko.ts index 309ba3ea21..bf3fa55b3f 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/ko.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/ko.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const koTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - '<0>{{fromTenant}}에서 <0>{{toTenant}}으로 소유권을 변경하려고 합니다.', - 'confirm-tenant-switch--heading': '{{tenantLabel}} 변경을 확인하세요', - 'field-assignedTentant-label': '지정된 세입자', + 'confirm-modal-tenant-switch--body': + '<0>{{fromTenant}}에서 <0>{{toTenant}}로 소유권을 변경하려고 합니다.', + 'confirm-modal-tenant-switch--heading': '{{tenantLabel}} 변경 확인', + 'field-assignedTenant-label': '지정된 세입자', + 'nav-tenantSelector-label': '세입자', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/lt.ts b/packages/plugin-multi-tenant/src/translations/languages/lt.ts index ad4cf34470..4b36dce6d0 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/lt.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/lt.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const ltTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'Jūs ketinate pakeisti nuosavybės teisę iš <0>{{fromTenant}} į <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Patvirtinkite {{tenantLabel}} pakeitimą', - 'field-assignedTentant-label': 'Paskirtas nuomininkas', + 'confirm-modal-tenant-switch--body': + 'Jūs ketinate pakeisti nuosavybę iš <0>{{fromTenant}} į <0>{{toTenant}}', + 'confirm-modal-tenant-switch--heading': 'Patvirtinkite {{tenantLabel}} pakeitimą', + 'field-assignedTenant-label': 'Paskirtas nuomininkas', + 'nav-tenantSelector-label': 'Nuomininkas', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/lv.ts b/packages/plugin-multi-tenant/src/translations/languages/lv.ts index 03b57339c8..54720124c6 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/lv.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/lv.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const lvTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': + 'confirm-modal-tenant-switch--body': 'Jūs gatavojaties mainīt īpašumtiesības no <0>{{fromTenant}} uz <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Apstipriniet {{tenantLabel}} izmaiņu', - 'field-assignedTentant-label': 'Piešķirts īrnieks', + 'confirm-modal-tenant-switch--heading': 'Apstipriniet {{tenantLabel}} izmaiņu', + 'field-assignedTenant-label': 'Piešķirts nomnieks', + 'nav-tenantSelector-label': 'Nomnieks', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/my.ts b/packages/plugin-multi-tenant/src/translations/languages/my.ts index 0642543d3b..06414b551d 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/my.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/my.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const myTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'Anda akan mengubah pemilikan dari <0>{{fromTenant}} ke <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Sahkan perubahan {{tenantLabel}}', - 'field-assignedTentant-label': 'ခွဲစိုက်ထားသော အငှားယူသူ', + 'confirm-modal-tenant-switch--body': + 'Anda akan menukar pemilikan dari <0>{{fromTenant}} kepada <0>{{toTenant}}', + 'confirm-modal-tenant-switch--heading': 'Sahkan perubahan {{tenantLabel}}', + 'field-assignedTenant-label': 'ခွဲစိုက်ထားသော အငှားယူသူ', + 'nav-tenantSelector-label': 'Penyewa', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/nb.ts b/packages/plugin-multi-tenant/src/translations/languages/nb.ts index ec1c8e5315..1485f4eb72 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/nb.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/nb.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const nbTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': + 'confirm-modal-tenant-switch--body': 'Du er i ferd med å endre eierskap fra <0>{{fromTenant}} til <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Bekreft {{tenantLabel}} endring', - 'field-assignedTentant-label': 'Tildelt leietaker', + 'confirm-modal-tenant-switch--heading': 'Bekreft endring av {{tenantLabel}}', + 'field-assignedTenant-label': 'Tildelt leietaker', + 'nav-tenantSelector-label': 'Leietaker', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/nl.ts b/packages/plugin-multi-tenant/src/translations/languages/nl.ts index 853c6a40d4..f57a27d82e 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/nl.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/nl.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const nlTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'U staat op het punt het eigendom te wijzigen van <0>{{fromTenant}} naar <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Bevestig wijziging van {{tenantLabel}}', - 'field-assignedTentant-label': 'Toegewezen Huurder', + 'confirm-modal-tenant-switch--body': + 'U staat op het punt om eigenaarschap te wijzigen van <0>{{fromTenant}} naar <0>{{toTenant}}', + 'confirm-modal-tenant-switch--heading': 'Bevestig wijziging van {{tenantLabel}}', + 'field-assignedTenant-label': 'Toegewezen Huurder', + 'nav-tenantSelector-label': 'Huurder', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/pl.ts b/packages/plugin-multi-tenant/src/translations/languages/pl.ts index a07ff219d9..2e8fd21974 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/pl.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/pl.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const plTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': + 'confirm-modal-tenant-switch--body': 'Za chwilę nastąpi zmiana właściciela z <0>{{fromTenant}} na <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Potwierdź zmianę {{tenantLabel}}', - 'field-assignedTentant-label': 'Przypisany Najemca', + 'confirm-modal-tenant-switch--heading': 'Potwierdź zmianę {{tenantLabel}}', + 'field-assignedTenant-label': 'Przypisany Najemca', + 'nav-tenantSelector-label': 'Najemca', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/pt.ts b/packages/plugin-multi-tenant/src/translations/languages/pt.ts index 7a1e2d758b..5ca864fb69 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/pt.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/pt.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const ptTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'Você está prestes a alterar a propriedade de <0>{{fromTenant}} para <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Confirme a alteração de {{tenantLabel}}', - 'field-assignedTentant-label': 'Inquilino Atribuído', + 'confirm-modal-tenant-switch--body': + 'Está prestes a mudar a propriedade de <0>{{fromTenant}} para <0>{{toTenant}}', + 'confirm-modal-tenant-switch--heading': 'Confirme a alteração do {{tenantLabel}}', + 'field-assignedTenant-label': 'Inquilino Atribuído', + 'nav-tenantSelector-label': 'Inquilino', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/ro.ts b/packages/plugin-multi-tenant/src/translations/languages/ro.ts index 95f6dd19c9..52bd9f85da 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/ro.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/ro.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const roTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'Sunteți pe punctul de a schimba proprietatea de la <0>{{fromTenant}} la <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Confirmați schimbarea {{tenantLabel}}', - 'field-assignedTentant-label': 'Locatar Atribuit', + 'confirm-modal-tenant-switch--body': + 'Sunteți pe cale să schimbați proprietatea de la <0>{{fromTenant}} la <0>{{toTenant}}', + 'confirm-modal-tenant-switch--heading': 'Confirmați modificarea {{tenantLabel}}', + 'field-assignedTenant-label': 'Locatar Atribuit', + 'nav-tenantSelector-label': 'Locatar', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/rs.ts b/packages/plugin-multi-tenant/src/translations/languages/rs.ts index dd76ce74e4..e9176c4e48 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/rs.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/rs.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const rsTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'Upravo ćete promeniti vlasništvo sa <0>{{fromTenant}} na <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Potvrdi promena {{tenantLabel}}', - 'field-assignedTentant-label': 'Dodeljen stanar', + 'confirm-modal-tenant-switch--body': + 'Na putu ste da promenite vlasništvo od <0>{{fromTenant}} do <0>{{toTenant}}', + 'confirm-modal-tenant-switch--heading': 'Potvrdite promenu {{tenantLabel}}', + 'field-assignedTenant-label': 'Dodeljen stanar', + 'nav-tenantSelector-label': 'Podstanar', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/rsLatin.ts b/packages/plugin-multi-tenant/src/translations/languages/rsLatin.ts index e381b0b5c2..aa744c021d 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/rsLatin.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/rsLatin.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const rsLatinTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': + 'confirm-modal-tenant-switch--body': 'Uskoro ćete promeniti vlasništvo sa <0>{{fromTenant}} na <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Potvrdite promenu {{tenantLabel}}', - 'field-assignedTentant-label': 'Dodeljen stanar', + 'confirm-modal-tenant-switch--heading': 'Potvrdite promenu {{tenantLabel}}', + 'field-assignedTenant-label': 'Dodeljen stanar', + 'nav-tenantSelector-label': 'Podstanar', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/ru.ts b/packages/plugin-multi-tenant/src/translations/languages/ru.ts index 6649ff4025..ab246ed30b 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/ru.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/ru.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const ruTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': + 'confirm-modal-tenant-switch--body': 'Вы собираетесь изменить владельца с <0>{{fromTenant}} на <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Подтвердите изменение {{tenantLabel}}', - 'field-assignedTentant-label': 'Назначенный Арендатор', + 'confirm-modal-tenant-switch--heading': 'Подтвердите изменение {{tenantLabel}}', + 'field-assignedTenant-label': 'Назначенный Арендатор', + 'nav-tenantSelector-label': 'Арендатор', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/sk.ts b/packages/plugin-multi-tenant/src/translations/languages/sk.ts index da565a6533..5664add516 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/sk.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/sk.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const skTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': + 'confirm-modal-tenant-switch--body': 'Chystáte sa zmeniť vlastníctvo z <0>{{fromTenant}} na <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Potvrďte zmenu {{tenantLabel}}', - 'field-assignedTentant-label': 'Pridelený nájomca', + 'confirm-modal-tenant-switch--heading': 'Potvrďte zmenu {{tenantLabel}}', + 'field-assignedTenant-label': 'Pridelený nájomca', + 'nav-tenantSelector-label': 'Nájomca', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/sl.ts b/packages/plugin-multi-tenant/src/translations/languages/sl.ts index 0726c47eca..40b2df7e74 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/sl.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/sl.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const slTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'Ravno ste pred spremembo lastništva iz <0>{{fromTenant}} na <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Potrdi spremembo {{tenantLabel}}', - 'field-assignedTentant-label': 'Dodeljen najemnik', + 'confirm-modal-tenant-switch--body': + 'Pravkar ste na točki, da spremenite lastništvo iz <0>{{fromTenant}} v <0>{{toTenant}}', + 'confirm-modal-tenant-switch--heading': 'Potrdite spremembo {{tenantLabel}}', + 'field-assignedTenant-label': 'Dodeljen najemnik', + 'nav-tenantSelector-label': 'Najemnik', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/sv.ts b/packages/plugin-multi-tenant/src/translations/languages/sv.ts index ee01da7591..418ef934f4 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/sv.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/sv.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const svTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'Du är på väg att ändra ägare från <0>{{fromTenant}} till <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Bekräfta ändring av {{tenantLabel}}', - 'field-assignedTentant-label': 'Tilldelad hyresgäst', + 'confirm-modal-tenant-switch--body': + 'Du är på väg att ändra ägande från <0>{{fromTenant}} till <0>{{toTenant}}', + 'confirm-modal-tenant-switch--heading': 'Bekräfta ändring av {{tenantLabel}}', + 'field-assignedTenant-label': 'Tilldelad hyresgäst', + 'nav-tenantSelector-label': 'Hyresgäst', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/th.ts b/packages/plugin-multi-tenant/src/translations/languages/th.ts index 2625f6aab5..78a8f4480f 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/th.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/th.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const thTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'คุณกำลังจะเปลี่ยนความเป็นเจ้าของจาก <0>{{fromTenant}} เป็น <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'ยืนยันการเปลี่ยนแปลง {{tenantLabel}}', - 'field-assignedTentant-label': 'ผู้เช่าที่ได้รับการกำหนด', + 'confirm-modal-tenant-switch--body': + 'คุณกำลังจะเปลี่ยนสิทธิ์การเป็นเจ้าของจาก <0>{{fromTenant}} ไปยัง <0>{{toTenant}}', + 'confirm-modal-tenant-switch--heading': 'ยืนยันการเปลี่ยนแปลง {{tenantLabel}}', + 'field-assignedTenant-label': 'ผู้เช่าที่ได้รับการกำหนด', + 'nav-tenantSelector-label': 'ผู้เช่า', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/tr.ts b/packages/plugin-multi-tenant/src/translations/languages/tr.ts index 6969d5e2d1..7bb284bce2 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/tr.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/tr.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const trTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - "Sahipliği <0>{{fromTenant}}'den <0>{{toTenant}}'e değiştirmek üzeresiniz.", - 'confirm-tenant-switch--heading': '{{tenantLabel}} değişikliğini onayla', - 'field-assignedTentant-label': 'Atanan Kiracı', + 'confirm-modal-tenant-switch--body': + "<0>{{fromTenant}}'den <0>{{toTenant}}'ye sahipliği değiştirmek üzeresiniz.", + 'confirm-modal-tenant-switch--heading': '{{tenantLabel}} değişikliğini onayla', + 'field-assignedTenant-label': 'Atanan Kiracı', + 'nav-tenantSelector-label': 'Kiracı', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/uk.ts b/packages/plugin-multi-tenant/src/translations/languages/uk.ts index 7dda25d194..40f921470b 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/uk.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/uk.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const ukTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'Ви збираєтесь змінити власність з <0>{{fromTenant}} на <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Підтвердіть зміну {{tenantLabel}}', - 'field-assignedTentant-label': 'Призначений орендар', + 'confirm-modal-tenant-switch--body': + 'Ви збираєтеся змінити власність з <0>{{fromTenant}} на <0>{{toTenant}}', + 'confirm-modal-tenant-switch--heading': 'Підтвердіть зміну {{tenantLabel}}', + 'field-assignedTenant-label': 'Призначений орендар', + 'nav-tenantSelector-label': 'Орендар', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/vi.ts b/packages/plugin-multi-tenant/src/translations/languages/vi.ts index d3e30dabb7..a9e28f8701 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/vi.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/vi.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const viTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - 'Bạn đang chuẩn bị chuyển quyền sở hữu từ <0>{{fromTenant}} sang <0>{{toTenant}}', - 'confirm-tenant-switch--heading': 'Xác nhận thay đổi {{tenantLabel}}', - 'field-assignedTentant-label': 'Người thuê đã được chỉ định', + 'confirm-modal-tenant-switch--body': + 'Bạn sắp chuyển quyền sở hữu từ <0>{{fromTenant}} đến <0>{{toTenant}}', + 'confirm-modal-tenant-switch--heading': 'Xác nhận thay đổi {{tenantLabel}}', + 'field-assignedTenant-label': 'Người thuê đã được chỉ định', + 'nav-tenantSelector-label': 'Người thuê', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/zh.ts b/packages/plugin-multi-tenant/src/translations/languages/zh.ts index 41255d9fb8..2bb02710cc 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/zh.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/zh.ts @@ -2,9 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const zhTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': '您即将将所有权从<0>{{fromTenant}}更改为<0>{{toTenant}}', - 'confirm-tenant-switch--heading': '确认更改{{tenantLabel}}', - 'field-assignedTentant-label': '指定租户', + 'confirm-modal-tenant-switch--body': + '您即将从<0>{{fromTenant}}更改为<0>{{toTenant}}的所有权', + 'confirm-modal-tenant-switch--heading': '确认更改{{tenantLabel}}', + 'field-assignedTenant-label': '指定租户', + 'nav-tenantSelector-label': '租户', }, } diff --git a/packages/plugin-multi-tenant/src/translations/languages/zhTw.ts b/packages/plugin-multi-tenant/src/translations/languages/zhTw.ts index 462691ddf4..8ff3cbd6a6 100644 --- a/packages/plugin-multi-tenant/src/translations/languages/zhTw.ts +++ b/packages/plugin-multi-tenant/src/translations/languages/zhTw.ts @@ -2,10 +2,11 @@ import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.j export const zhTwTranslations: PluginDefaultTranslationsObject = { 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': - '您即將變更擁有者,從 <0>{{fromTenant}} 切換為 <0>{{toTenant}}', - 'confirm-tenant-switch--heading': '確認變更 {{tenantLabel}}', - 'field-assignedTentant-label': '指派的租用戶', + 'confirm-modal-tenant-switch--body': + '您即將從<0>{{fromTenant}}變更所有權至<0>{{toTenant}}', + 'confirm-modal-tenant-switch--heading': '確認變更 {{tenantLabel}}', + 'field-assignedTenant-label': '指派的租用戶', + 'nav-tenantSelector-label': '租戶', }, } diff --git a/packages/plugin-multi-tenant/src/translations/types.ts b/packages/plugin-multi-tenant/src/translations/types.ts index 60daa75d71..4f8b5079ba 100644 --- a/packages/plugin-multi-tenant/src/translations/types.ts +++ b/packages/plugin-multi-tenant/src/translations/types.ts @@ -4,9 +4,10 @@ import type { enTranslations } from './languages/en.js' export type PluginLanguage = Language<{ 'plugin-multi-tenant': { - 'confirm-tenant-switch--body': string - 'confirm-tenant-switch--heading': string - 'field-assignedTentant-label': string + 'confirm-modal-tenant-switch--body': string + 'confirm-modal-tenant-switch--heading': string + 'field-assignedTenant-label': string + 'nav-tenantSelector-label': string } }> diff --git a/packages/plugin-multi-tenant/src/types.ts b/packages/plugin-multi-tenant/src/types.ts index 1f0e37236a..7b87c90a3e 100644 --- a/packages/plugin-multi-tenant/src/types.ts +++ b/packages/plugin-multi-tenant/src/types.ts @@ -1,5 +1,12 @@ import type { AcceptedLanguages } from '@payloadcms/translations' -import type { ArrayField, CollectionSlug, Field, RelationshipField, TypedUser } from 'payload' +import type { + ArrayField, + CollectionSlug, + Field, + RelationshipField, + SingleRelationshipField, + TypedUser, +} from 'payload' export type MultiTenantPluginConfig = { /** @@ -30,6 +37,11 @@ export type MultiTenantPluginConfig = { */ isGlobal?: boolean /** + * Overrides for the tenant field, will override the entire tenantField configuration + */ + tenantFieldOverrides?: CollectionTenantFieldConfigOverrides + /** + * Set to `false` if you want to manually apply the baseListFilter * Set to `false` if you want to manually apply the baseFilter * * @default true @@ -68,18 +80,37 @@ export type MultiTenantPluginConfig = { * @default true */ enabled?: boolean + /** + * Localization for the plugin + */ + i18n?: { + translations: { + [key in AcceptedLanguages]?: { + /** + * @default 'You are about to change ownership from <0>{{fromTenant}} to <0>{{toTenant}}' + */ + 'confirm-modal-tenant-switch--body'?: string + /** + * `tenantLabel` defaults to the value of the `nav-tenantSelector-label` translation + * + * @default 'Confirm {{tenantLabel}} change' + */ + 'confirm-modal-tenant-switch--heading'?: string + /** + * @default 'Assigned Tenant' + */ + 'field-assignedTenant-label'?: string + /** + * @default 'Tenant' + */ + 'nav-tenantSelector-label'?: string + } + } + } /** * Field configuration for the field added to all tenant enabled collections */ - tenantField?: { - access?: RelationshipField['access'] - /** - * The name of the field added to all tenant enabled collections - * - * @default 'tenant' - */ - name?: string - } + tenantField?: RootTenantFieldConfigOverrides /** * Field configuration for the field added to the users collection * @@ -132,6 +163,8 @@ export type MultiTenantPluginConfig = { * Customize tenant selector label * * Either a string or an object where the keys are i18n codes and the values are the string labels + * + * @deprecated Use `i18n.translations` instead. */ tenantSelectorLabel?: | Partial<{ @@ -166,6 +199,30 @@ export type MultiTenantPluginConfig = { useUsersTenantFilter?: boolean } +export type RootTenantFieldConfigOverrides = Partial< + Omit< + SingleRelationshipField, + | '_sanitized' + | 'hasMany' + | 'hidden' + | 'index' + | 'localized' + | 'max' + | 'maxRows' + | 'min' + | 'minRows' + | 'relationTo' + | 'required' + | 'type' + | 'unique' + | 'virtual' + > +> + +export type CollectionTenantFieldConfigOverrides = Partial< + Omit +> + export type Tenant = { id: IDType name: string diff --git a/packages/richtext-lexical/src/features/migrations/slateToLexical/converter/index.ts b/packages/richtext-lexical/src/features/migrations/slateToLexical/converter/index.ts index de3e59f625..bb17437412 100644 --- a/packages/richtext-lexical/src/features/migrations/slateToLexical/converter/index.ts +++ b/packages/richtext-lexical/src/features/migrations/slateToLexical/converter/index.ts @@ -124,7 +124,7 @@ export function convertTextNode(node: SlateNode): SerializedTextNode { format: convertNodeToFormat(node), mode: 'normal', style: '', - text: node.text ?? "", + text: node.text ?? '', version: 1, } } diff --git a/packages/translations/src/languages/zhTw.ts b/packages/translations/src/languages/zhTw.ts index 749186aaa0..52bd3cbf41 100644 --- a/packages/translations/src/languages/zhTw.ts +++ b/packages/translations/src/languages/zhTw.ts @@ -25,14 +25,14 @@ export const zhTwTranslations: DefaultTranslationsObject = { failedToUnlock: '解除鎖定失敗', forceUnlock: '強制解除鎖定', forgotPassword: '忘記密碼', - forgotPasswordEmailInstructions: - '請輸入您的電子郵件。您將會收到一封包含密碼重設指示的郵件。', + forgotPasswordEmailInstructions: '請輸入您的電子郵件。您將會收到一封包含密碼重設指示的郵件。', forgotPasswordQuestion: '忘記密碼?', forgotPasswordUsernameInstructions: '請輸入您的使用者名稱。重設密碼的指示將會寄送至該帳號所綁定的電子郵件。', generate: '產生', generateNewAPIKey: '產生新的 API 金鑰', - generatingNewAPIKeyWillInvalidate: '產生新的 API 金鑰將會使原本的金鑰<1>失效。確定要繼續嗎?', + generatingNewAPIKeyWillInvalidate: + '產生新的 API 金鑰將會使原本的金鑰<1>失效。確定要繼續嗎?', lockUntil: '鎖定至', logBackIn: '重新登入', loggedIn: '若要使用其他使用者登入,請先<0>登出。', @@ -187,8 +187,10 @@ export const zhTwTranslations: DefaultTranslationsObject = { moveFolder: '移動資料夾', moveItemsToFolderConfirmation: '您即將移動 <1>{{count}} 個 {{label}} 到 <2>{{toFolder}}。確定要繼續?', - moveItemsToRootConfirmation: '您即將移動 <1>{{count}} 個 {{label}} 到根資料夾。確定要繼續?', - moveItemToFolderConfirmation: '您即將移動 <1>{{title}} 到 <2>{{toFolder}}。確定要繼續?', + moveItemsToRootConfirmation: + '您即將移動 <1>{{count}} 個 {{label}} 到根資料夾。確定要繼續?', + moveItemToFolderConfirmation: + '您即將移動 <1>{{title}} 到 <2>{{toFolder}}。確定要繼續?', moveItemToRootConfirmation: '您即將移動 <1>{{title}} 到根資料夾。確定要繼續?', movingFromFolder: '正在從 {{fromFolder}} 移動 {{title}}', newFolder: '新增資料夾', @@ -390,8 +392,7 @@ export const zhTwTranslations: DefaultTranslationsObject = { success: '成功', successfullyCreated: '已成功建立 {{label}}。', successfullyDuplicated: '已成功複製 {{label}}。', - successfullyReindexed: - '已成功重新索引 {{collections}} 中 {{total}} 筆文件中的 {{count}} 筆。', + successfullyReindexed: '已成功重新索引 {{collections}} 中 {{total}} 筆文件中的 {{count}} 筆。', takeOver: '接手編輯', thisLanguage: '中文(繁體)', time: '時間', diff --git a/test/plugin-multi-tenant/config.ts b/test/plugin-multi-tenant/config.ts index a047a051bf..a57daf85c7 100644 --- a/test/plugin-multi-tenant/config.ts +++ b/test/plugin-multi-tenant/config.ts @@ -44,7 +44,15 @@ export default buildConfigWithDefaults({ isGlobal: true, }, }, - tenantSelectorLabel: { en: 'Site', es: 'Site in es' }, + i18n: { + translations: { + en: { + 'field-assignedTenant-label': 'Currently Assigned Site', + 'nav-tenantSelector-label': 'Filter By Site', + 'confirm-modal-tenant-switch--heading': 'Confirm Site Change', + }, + }, + }, }), ], typescript: { From ad2564e5fa465e74bc409df97127d3b99eb91e3a Mon Sep 17 00:00:00 2001 From: Alessio Gravili Date: Tue, 12 Aug 2025 08:55:17 -0700 Subject: [PATCH 07/14] fix: ensure scheduling by default only handles default queue, add allQueues config to autoRun (#13395) By default, `payload.jobs.run` only runs jobs from the `default` queue (since https://github.com/payloadcms/payload/pull/12799). It exposes an `allQueues` property to run jobs from all queues. For handling schedules (`payload.jobs.handleSchedules` and `config.jobs.autoRun`), this behaves differently - jobs are run from all queues by default, and no `allQueues` property exists. This PR adds an `allQueues` property to scheduling, as well as changes the default behavior to only handle schedules for the `default` queue. That way, the behavior of running and scheduling jobs matches. --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1210982048221260 --- .../src/fields/baseFields/baseIDField.ts | 1 + .../fields/hooks/beforeDuplicate/promise.ts | 3 +- packages/payload/src/index.ts | 2 + .../payload/src/queues/config/types/index.ts | 9 +++ .../src/queues/endpoints/handleSchedules.ts | 11 +++- packages/payload/src/queues/endpoints/run.ts | 2 +- packages/payload/src/queues/localAPI.ts | 10 +++- .../operations/handleSchedules/index.ts | 15 ++++- test/queues/schedules.int.spec.ts | 55 ++++++++++++++++--- 9 files changed, 93 insertions(+), 15 deletions(-) diff --git a/packages/payload/src/fields/baseFields/baseIDField.ts b/packages/payload/src/fields/baseFields/baseIDField.ts index f83f0b7b0a..5b488efcd6 100644 --- a/packages/payload/src/fields/baseFields/baseIDField.ts +++ b/packages/payload/src/fields/baseFields/baseIDField.ts @@ -14,6 +14,7 @@ export const baseIDField: TextField = { defaultValue: () => new ObjectId().toHexString(), hooks: { beforeChange: [({ value }) => value || new ObjectId().toHexString()], + // ID field values for arrays and blocks need to be unique when duplicating, as on postgres they are stored on the same table as primary keys. beforeDuplicate: [() => new ObjectId().toHexString()], }, label: 'ID', diff --git a/packages/payload/src/fields/hooks/beforeDuplicate/promise.ts b/packages/payload/src/fields/hooks/beforeDuplicate/promise.ts index 58fce1e2e4..337f2f82a9 100644 --- a/packages/payload/src/fields/hooks/beforeDuplicate/promise.ts +++ b/packages/payload/src/fields/hooks/beforeDuplicate/promise.ts @@ -63,7 +63,8 @@ export const promise = async ({ let fieldData = siblingDoc?.[field.name!] const fieldIsLocalized = localization && fieldShouldBeLocalized({ field, parentIsLocalized }) - // Run field beforeDuplicate hooks + // Run field beforeDuplicate hooks. + // These hooks are responsible for resetting the `id` field values of array and block rows. See `baseIDField`. if (Array.isArray(field.hooks?.beforeDuplicate)) { if (fieldIsLocalized) { const localeData: JsonObject = {} diff --git a/packages/payload/src/index.ts b/packages/payload/src/index.ts index a780a3128d..d23a502f40 100644 --- a/packages/payload/src/index.ts +++ b/packages/payload/src/index.ts @@ -873,6 +873,7 @@ export class BasePayload { this.config.jobs.scheduling ) { await this.jobs.handleSchedules({ + allQueues: cronConfig.allQueues, queue: cronConfig.queue, }) } @@ -891,6 +892,7 @@ export class BasePayload { } await this.jobs.run({ + allQueues: cronConfig.allQueues, limit: cronConfig.limit ?? DEFAULT_LIMIT, queue: cronConfig.queue, silent: cronConfig.silent, diff --git a/packages/payload/src/queues/config/types/index.ts b/packages/payload/src/queues/config/types/index.ts index 9ea4ff2233..e3363687a5 100644 --- a/packages/payload/src/queues/config/types/index.ts +++ b/packages/payload/src/queues/config/types/index.ts @@ -7,6 +7,13 @@ import type { TaskConfig } from './taskTypes.js' import type { WorkflowConfig } from './workflowTypes.js' export type AutorunCronConfig = { + /** + * If you want to autoRUn jobs from all queues, set this to true. + * If you set this to true, the `queue` property will be ignored. + * + * @default false + */ + allQueues?: boolean /** * The cron schedule for the job. * @default '* * * * *' (every minute). @@ -43,6 +50,8 @@ export type AutorunCronConfig = { limit?: number /** * The queue name for the job. + * + * @default 'default' */ queue?: string /** diff --git a/packages/payload/src/queues/endpoints/handleSchedules.ts b/packages/payload/src/queues/endpoints/handleSchedules.ts index 385cb496e9..339731a335 100644 --- a/packages/payload/src/queues/endpoints/handleSchedules.ts +++ b/packages/payload/src/queues/endpoints/handleSchedules.ts @@ -45,11 +45,18 @@ export const handleSchedulesJobsEndpoint: Endpoint = { ) } - const { queue } = req.query as { + const { allQueues, queue } = req.query as { + allQueues?: 'false' | 'true' queue?: string } - const { errored, queued, skipped } = await handleSchedules({ queue, req }) + const runAllQueues = allQueues && !(typeof allQueues === 'string' && allQueues === 'false') + + const { errored, queued, skipped } = await handleSchedules({ + allQueues: runAllQueues, + queue, + req, + }) return Response.json( { diff --git a/packages/payload/src/queues/endpoints/run.ts b/packages/payload/src/queues/endpoints/run.ts index a362a7d2cc..d1785e55cd 100644 --- a/packages/payload/src/queues/endpoints/run.ts +++ b/packages/payload/src/queues/endpoints/run.ts @@ -56,7 +56,7 @@ export const runJobsEndpoint: Endpoint = { if (shouldHandleSchedules && jobsConfig.scheduling) { // If should handle schedules and schedules are defined - await req.payload.jobs.handleSchedules({ queue: runAllQueues ? undefined : queue, req }) + await req.payload.jobs.handleSchedules({ allQueues: runAllQueues, queue, req }) } const runJobsArgs: RunJobsArgs = { diff --git a/packages/payload/src/queues/localAPI.ts b/packages/payload/src/queues/localAPI.ts index c38a64868f..732f0970ed 100644 --- a/packages/payload/src/queues/localAPI.ts +++ b/packages/payload/src/queues/localAPI.ts @@ -22,13 +22,20 @@ export type RunJobsSilent = | boolean export const getJobsLocalAPI = (payload: Payload) => ({ handleSchedules: async (args?: { + /** + * If you want to schedule jobs from all queues, set this to true. + * If you set this to true, the `queue` property will be ignored. + * + * @default false + */ + allQueues?: boolean // By default, schedule all queues - only scheduling jobs scheduled to be added to the `default` queue would not make sense // here, as you'd usually specify a different queue than `default` here, especially if this is used in combination with autorun. // The `queue` property for setting up schedules is required, and not optional. /** * If you want to only schedule jobs that are set to schedule in a specific queue, set this to the queue name. * - * @default all jobs for all queues will be scheduled. + * @default jobs from the `default` queue will be executed. */ queue?: string req?: PayloadRequest @@ -36,6 +43,7 @@ export const getJobsLocalAPI = (payload: Payload) => ({ const newReq: PayloadRequest = args?.req ?? (await createLocalReq({}, payload)) return await handleSchedules({ + allQueues: args?.allQueues, queue: args?.queue, req: newReq, }) diff --git a/packages/payload/src/queues/operations/handleSchedules/index.ts b/packages/payload/src/queues/operations/handleSchedules/index.ts index b5daefccb9..53f76d8ef3 100644 --- a/packages/payload/src/queues/operations/handleSchedules/index.ts +++ b/packages/payload/src/queues/operations/handleSchedules/index.ts @@ -23,17 +23,26 @@ export type HandleSchedulesResult = { * after they are scheduled */ export async function handleSchedules({ - queue, + allQueues = false, + queue: _queue, req, }: { + /** + * If you want to schedule jobs from all queues, set this to true. + * If you set this to true, the `queue` property will be ignored. + * + * @default false + */ + allQueues?: boolean /** * If you want to only schedule jobs that are set to schedule in a specific queue, set this to the queue name. * - * @default all jobs for all queues will be scheduled. + * @default jobs from the `default` queue will be executed. */ queue?: string req: PayloadRequest }): Promise { + const queue = _queue ?? 'default' const jobsConfig = req.payload.config.jobs const queuesWithSchedules = getQueuesWithSchedules({ jobsConfig, @@ -53,7 +62,7 @@ export async function handleSchedules({ // Need to know when that particular job was last scheduled in that particular queue for (const [queueName, { schedules }] of Object.entries(queuesWithSchedules)) { - if (queue && queueName !== queue) { + if (!allQueues && queueName !== queue) { // If a queue is specified, only schedule jobs for that queue continue } diff --git a/test/queues/schedules.int.spec.ts b/test/queues/schedules.int.spec.ts index f4fa8a3ed8..51d029600f 100644 --- a/test/queues/schedules.int.spec.ts +++ b/test/queues/schedules.int.spec.ts @@ -69,7 +69,7 @@ describe('Queues - scheduling, without automatic scheduling handling', () => { it('can auto-schedule through local API and autorun jobs', async () => { // Do not call payload.jobs.queue() - the `EverySecond` task should be scheduled here - await payload.jobs.handleSchedules() + await payload.jobs.handleSchedules({ queue: 'autorunSecond' }) // Do not call payload.jobs.run{silent: true}) @@ -88,9 +88,50 @@ describe('Queues - scheduling, without automatic scheduling handling', () => { expect(allSimples?.docs?.[0]?.title).toBe('This task runs every second') }) + it('can auto-schedule through local API and autorun jobs when passing allQueues', async () => { + // Do not call payload.jobs.queue() - the `EverySecond` task should be scheduled here + await payload.jobs.handleSchedules({ queue: 'autorunSecond', allQueues: true }) + + // Do not call payload.jobs.run{silent: true}) + + await waitUntilAutorunIsDone({ + payload, + queue: 'autorunSecond', + onlyScheduled: true, + }) + + const allSimples = await payload.find({ + collection: 'simple', + limit: 100, + }) + + expect(allSimples.totalDocs).toBe(1) + expect(allSimples?.docs?.[0]?.title).toBe('This task runs every second') + }) + + it('should not auto-schedule through local API and autorun jobs when not passing queue and schedule is not set on the default queue', async () => { + // Do not call payload.jobs.queue() - the `EverySecond` task should be scheduled here + await payload.jobs.handleSchedules() + + // Do not call payload.jobs.run{silent: true}) + + await waitUntilAutorunIsDone({ + payload, + queue: 'autorunSecond', + onlyScheduled: true, + }) + + const allSimples = await payload.find({ + collection: 'simple', + limit: 100, + }) + + expect(allSimples.totalDocs).toBe(0) + }) + it('can auto-schedule through handleSchedules REST API and autorun jobs', async () => { // Do not call payload.jobs.queue() - the `EverySecond` task should be scheduled here - await restClient.GET('/payload-jobs/handle-schedules', { + await restClient.GET('/payload-jobs/handle-schedules?queue=autorunSecond', { headers: { Authorization: `JWT ${token}`, }, @@ -115,7 +156,7 @@ describe('Queues - scheduling, without automatic scheduling handling', () => { it('can auto-schedule through run REST API and autorun jobs', async () => { // Do not call payload.jobs.queue() - the `EverySecond` task should be scheduled here - await restClient.GET('/payload-jobs/run?silent=true', { + await restClient.GET('/payload-jobs/run?silent=true&allQueues=true', { headers: { Authorization: `JWT ${token}`, }, @@ -161,7 +202,7 @@ describe('Queues - scheduling, without automatic scheduling handling', () => { it('ensure scheduler does not schedule more jobs than needed if executed sequentially', async () => { await withoutAutoRun(async () => { for (let i = 0; i < 3; i++) { - await payload.jobs.handleSchedules() + await payload.jobs.handleSchedules({ allQueues: true }) } }) @@ -192,7 +233,7 @@ describe('Queues - scheduling, without automatic scheduling handling', () => { }) } for (let i = 0; i < 3; i++) { - await payload.jobs.handleSchedules() + await payload.jobs.handleSchedules({ allQueues: true }) } }) @@ -271,8 +312,8 @@ describe('Queues - scheduling, without automatic scheduling handling', () => { for (let i = 0; i < 3; i++) { await withoutAutoRun(async () => { // Call it twice to test that it only schedules one - await payload.jobs.handleSchedules() - await payload.jobs.handleSchedules() + await payload.jobs.handleSchedules({ allQueues: true }) + await payload.jobs.handleSchedules({ allQueues: true }) }) // Advance time to satisfy the waitUntil of newly scheduled jobs timeTravel(20) From 3258e78596fa29b739114f598d5665c84da8b606 Mon Sep 17 00:00:00 2001 From: Patrik <35232443+PatrikKozak@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:49:47 -0400 Subject: [PATCH 08/14] test: group-by reset and navigation tests in trash view (#13401) ### What? Adds e2e tests that verify group-by functionality within the trash view of a collection. ### Why? To ensure group-by behaves correctly when viewing soft-deleted documents, including: - Clearing the group-by selection via the reset button. - Navigating from grouped rows to the trashed document's edit view. ### How? - Added `should properly clear group-by in trash view` to test the reset button behavior. - Added `should properly navigate to trashed doc edit view from group-by in trash view` to confirm correct linking and routing. --- test/group-by/e2e.spec.ts | 99 +++++++++++++++++++++++++++------------ 1 file changed, 68 insertions(+), 31 deletions(-) diff --git a/test/group-by/e2e.spec.ts b/test/group-by/e2e.spec.ts index 2f9cfeaace..29b13b928f 100644 --- a/test/group-by/e2e.spec.ts +++ b/test/group-by/e2e.spec.ts @@ -14,7 +14,7 @@ import { reInitializeDB } from 'helpers/reInitializeDB.js' import * as path from 'path' import { fileURLToPath } from 'url' -import type { Config } from './payload-types.js' +import type { Config, Post } from './payload-types.js' import { ensureCompilationIsDone, @@ -38,7 +38,6 @@ test.describe('Group By', () => { let serverURL: string let payload: PayloadTestSDK let user: any - let category1Id: number | string test.beforeAll(async ({ browser }, testInfo) => { testInfo.setTimeout(TEST_TIMEOUT_LONG) @@ -695,42 +694,80 @@ test.describe('Group By', () => { ).toHaveCount(0) }) - test('should show trashed docs in trash view when group-by is active', async () => { - await page.goto(url.list) + test.describe('Trash', () => { + test('should show trashed docs in trash view when group-by is active', async () => { + await page.goto(url.list) - // Enable group-by on Category - await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category' }) - await expect(page.locator('.table-wrap')).toHaveCount(2) // We expect 2 groups initially + // Enable group-by on Category + await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category' }) + await expect(page.locator('.table-wrap')).toHaveCount(2) // We expect 2 groups initially - // Trash the first document in the first group - const firstTable = page.locator('.table-wrap').first() - await firstTable.locator('.row-1 .cell-_select input').check() - await firstTable.locator('.list-selection__button[aria-label="Delete"]').click() + // Trash the first document in the first group + const firstTable = page.locator('.table-wrap').first() + await firstTable.locator('.row-1 .cell-_select input').check() + await firstTable.locator('.list-selection__button[aria-label="Delete"]').click() - const firstGroupID = await firstTable - .locator('.group-by-header__heading') - .getAttribute('data-group-id') + const firstGroupID = await firstTable + .locator('.group-by-header__heading') + .getAttribute('data-group-id') - const modalId = `[id^="${firstGroupID}-confirm-delete-many-docs"]` - await expect(page.locator(modalId)).toBeVisible() + const modalId = `[id^="${firstGroupID}-confirm-delete-many-docs"]` + await expect(page.locator(modalId)).toBeVisible() - // Confirm trash (skip permanent delete) - await page.locator(`${modalId} #confirm-action`).click() - await expect(page.locator('.payload-toast-container .toast-success')).toHaveText( - '1 Post moved to trash.', - ) + // Confirm trash (skip permanent delete) + await page.locator(`${modalId} #confirm-action`).click() + await expect(page.locator('.payload-toast-container .toast-success')).toHaveText( + '1 Post moved to trash.', + ) - // Go to the trash view - await page.locator('#trash-view-pill').click() - await expect(page).toHaveURL(/\/posts\/trash(\?|$)/) + // Go to the trash view + await page.locator('#trash-view-pill').click() + await expect(page).toHaveURL(/\/posts\/trash(\?|$)/) - // Re-enable group-by on Category in trash view - await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category' }) - await expect(page.locator('.table-wrap')).toHaveCount(1) // Should only have Category 1 (or the trashed doc's category) + // Re-enable group-by on Category in trash view + await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category' }) + await expect(page.locator('.table-wrap')).toHaveCount(1) // Should only have Category 1 (or the trashed doc's category) - // Ensure the trashed doc is visible - await expect( - page.locator('.table-wrap tbody tr td.cell-title', { hasText: 'Find me' }), - ).toBeVisible() + // Ensure the trashed doc is visible + await expect( + page.locator('.table-wrap tbody tr td.cell-title', { hasText: 'Find me' }), + ).toBeVisible() + }) + + test('should properly clear group-by in trash view', async () => { + await createTrashedPostDoc({ title: 'Trashed Post 1' }) + await page.goto(url.trash) + + // Enable group-by on Title + await addGroupBy(page, { fieldLabel: 'Title', fieldPath: 'title' }) + await expect(page.locator('.table-wrap')).toHaveCount(1) + await expect(page.locator('.group-by-header')).toHaveText('Trashed Post 1') + + await page.locator('#group-by--reset').click() + await expect(page.locator('.group-by-header')).toBeHidden() + }) + + test('should properly navigate to trashed doc edit view from group-by in trash view', async () => { + await createTrashedPostDoc({ title: 'Trashed Post 1' }) + await page.goto(url.trash) + + // Enable group-by on Title + await addGroupBy(page, { fieldLabel: 'Title', fieldPath: 'title' }) + await expect(page.locator('.table-wrap')).toHaveCount(1) + await expect(page.locator('.group-by-header')).toHaveText('Trashed Post 1') + + await page.locator('.table-wrap tbody tr td.cell-title a').click() + await expect(page).toHaveURL(/\/posts\/trash\/\d+/) + }) }) + + async function createTrashedPostDoc(data: Partial): Promise { + return payload.create({ + collection: postsSlug, + data: { + ...data, + deletedAt: new Date().toISOString(), // Set the post as trashed + }, + }) as unknown as Promise + } }) From 8173180d1d0b41f116a9054705f6d793fddb6104 Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Tue, 12 Aug 2025 14:28:04 -0400 Subject: [PATCH 09/14] fix(ui): autosave form state discards local changes (#13438) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to #13416. Supersedes #13434. When autosave is triggered and the user continues to modify fields, their changes are overridden by the server's value, i.e. the value at the time the form state request was made. This makes it almost impossible to edit fields when using a small autosave interval and/or a slow network. This is because autosave is now merged into form state, which by default uses `acceptValues: true`. This does exactly what it sounds like, accepts all the values from the server—which may be stale if underlying changes have been made. We ignore these values for onChange events, because the user is actively making changes. But during form submissions, we can accept them because the form is disabled while processing anyway. This pattern allows us to render "computed values" from the server, i.e. a field with an `beforeChange` hook that modifies its value. Autosave, on the other hand, happens in the background _while the form is still active_. This means changes may have been made since sending the request. We still need to accept computed values from the server, but we need to avoid doing this if the user has active changes since the time of the request. --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1211027929253429 --- packages/ui/src/elements/Autosave/index.tsx | 3 + packages/ui/src/forms/Form/fieldReducer.ts | 3 +- packages/ui/src/forms/Form/index.tsx | 31 +++--- .../ui/src/forms/Form/mergeServerFormState.ts | 34 +++++- packages/ui/src/forms/Form/types.ts | 14 ++- .../addFieldStatePromise.ts | 4 +- test/form-state/int.spec.ts | 105 ++++++++++++++++++ test/versions/collections/Autosave.ts | 12 +- test/versions/e2e.spec.ts | 21 +++- test/versions/payload-types.ts | 12 ++ 10 files changed, 214 insertions(+), 25 deletions(-) diff --git a/packages/ui/src/elements/Autosave/index.tsx b/packages/ui/src/elements/Autosave/index.tsx index f341efa21e..96b121792d 100644 --- a/packages/ui/src/elements/Autosave/index.tsx +++ b/packages/ui/src/elements/Autosave/index.tsx @@ -150,6 +150,9 @@ export const Autosave: React.FC = ({ id, collection, global: globalDoc }) if (!skipSubmission && modifiedRef.current && url) { const result = await submit({ + acceptValues: { + overrideLocalChanges: false, + }, action: url, context: { incrementVersionCount: false, diff --git a/packages/ui/src/forms/Form/fieldReducer.ts b/packages/ui/src/forms/Form/fieldReducer.ts index 9557376998..a2910cee89 100644 --- a/packages/ui/src/forms/Form/fieldReducer.ts +++ b/packages/ui/src/forms/Form/fieldReducer.ts @@ -189,11 +189,12 @@ export function fieldReducer(state: FormState, action: FieldAction): FormState { } case 'MERGE_SERVER_STATE': { - const { acceptValues, prevStateRef, serverState } = action + const { acceptValues, formStateAtTimeOfRequest, prevStateRef, serverState } = action const newState = mergeServerFormState({ acceptValues, currentState: state || {}, + formStateAtTimeOfRequest, incomingState: serverState, }) diff --git a/packages/ui/src/forms/Form/index.tsx b/packages/ui/src/forms/Form/index.tsx index ddb1cb115d..aa545e4999 100644 --- a/packages/ui/src/forms/Form/index.tsx +++ b/packages/ui/src/forms/Form/index.tsx @@ -203,6 +203,7 @@ export const Form: React.FC = (props) => { const submit = useCallback( async (options, e) => { const { + acceptValues = true, action: actionArg = action, context, disableFormWhileProcessing = true, @@ -263,19 +264,23 @@ export const Form: React.FC = (props) => { await wait(100) } + /** + * Take copies of the current form state and data here. This will ensure it is consistent. + * For example, it is possible for the form state ref to change in the background while this submit function is running. + * TODO: can we send the `formStateCopy` through `reduceFieldsToValues` to even greater consistency? Doing this currently breaks uploads. + */ + const formStateCopy = deepCopyObjectSimpleWithoutReactComponents(contextRef.current.fields) + const data = reduceFieldsToValues(contextRef.current.fields, true) + // Execute server side validations if (Array.isArray(beforeSubmit)) { let revalidatedFormState: FormState - const serializableFields = deepCopyObjectSimpleWithoutReactComponents( - contextRef.current.fields, - ) - await beforeSubmit.reduce(async (priorOnChange, beforeSubmitFn) => { await priorOnChange const result = await beforeSubmitFn({ - formState: serializableFields, + formState: formStateCopy, }) revalidatedFormState = result @@ -319,17 +324,11 @@ export const Form: React.FC = (props) => { // If submit handler comes through via props, run that if (onSubmit) { - const serializableFields = deepCopyObjectSimpleWithoutReactComponents( - contextRef.current.fields, - ) - - const data = reduceFieldsToValues(serializableFields, true) - for (const [key, value] of Object.entries(overrides)) { data[key] = value } - onSubmit(serializableFields, data) + onSubmit(formStateCopy, data) } if (!hasFormSubmitAction) { @@ -342,6 +341,7 @@ export const Form: React.FC = (props) => { } const formData = await contextRef.current.createFormData(overrides, { + data, mergeOverrideData: Boolean(typeof overridesFromArgs !== 'function'), }) @@ -384,7 +384,8 @@ export const Form: React.FC = (props) => { if (newFormState) { dispatchFields({ type: 'MERGE_SERVER_STATE', - acceptValues: true, + acceptValues, + formStateAtTimeOfRequest: formStateCopy, prevStateRef: prevFormState, serverState: newFormState, }) @@ -503,8 +504,8 @@ export const Form: React.FC = (props) => { ) const createFormData = useCallback( - async (overrides, { mergeOverrideData = true }) => { - let data = reduceFieldsToValues(contextRef.current.fields, true) + async (overrides, { data: dataFromArgs, mergeOverrideData = true }) => { + let data = dataFromArgs || reduceFieldsToValues(contextRef.current.fields, true) let file = data?.file diff --git a/packages/ui/src/forms/Form/mergeServerFormState.ts b/packages/ui/src/forms/Form/mergeServerFormState.ts index 5d0bd2a2f1..adfd6062ff 100644 --- a/packages/ui/src/forms/Form/mergeServerFormState.ts +++ b/packages/ui/src/forms/Form/mergeServerFormState.ts @@ -3,9 +3,25 @@ import type { FormState } from 'payload' import { dequal } from 'dequal/lite' // lite: no need for Map and Set support +/** + * If true, will accept all values from the server, overriding any current values in local state. + * Can also provide an options object for more granular control. + */ +export type AcceptValues = + | { + /** + * When `false`, will accept the values from the server _UNLESS_ the value has been modified locally since the request was made. + * This is useful for autosave, for example, where hooks may have modified the field's value on the server while you were still making changes. + * @default undefined + */ + overrideLocalChanges?: boolean + } + | boolean + type Args = { - acceptValues?: boolean + acceptValues?: AcceptValues currentState?: FormState + formStateAtTimeOfRequest?: FormState incomingState: FormState } @@ -21,6 +37,7 @@ type Args = { export const mergeServerFormState = ({ acceptValues, currentState = {}, + formStateAtTimeOfRequest, incomingState, }: Args): FormState => { const newState = { ...currentState } @@ -30,7 +47,20 @@ export const mergeServerFormState = ({ continue } - if (!acceptValues && !incomingField.addedByServer) { + /** + * If it's a new field added by the server, always accept the value. + * Otherwise, only accept the values if explicitly requested, e.g. on submit. + * Can also control this granularly by only accepting unmodified values, e.g. for autosave. + */ + if ( + !incomingField.addedByServer && + (!acceptValues || + // See `acceptValues` type definition for more details + (typeof acceptValues === 'object' && + acceptValues !== null && + acceptValues?.overrideLocalChanges === false && + currentState[path]?.value !== formStateAtTimeOfRequest?.[path]?.value)) + ) { delete incomingField.value delete incomingField.initialValue } diff --git a/packages/ui/src/forms/Form/types.ts b/packages/ui/src/forms/Form/types.ts index a10350506a..a52ed34662 100644 --- a/packages/ui/src/forms/Form/types.ts +++ b/packages/ui/src/forms/Form/types.ts @@ -10,6 +10,8 @@ import type { import type React from 'react' import type { Dispatch } from 'react' +import type { AcceptValues } from './mergeServerFormState.js' + export type Preferences = { [key: string]: unknown } @@ -69,6 +71,7 @@ export type FormProps = { ) export type SubmitOptions = { + acceptValues?: AcceptValues action?: string /** * @experimental - Note: this property is experimental and may change in the future. Use as your own discretion. @@ -113,7 +116,13 @@ export type CreateFormData = ( * If mergeOverrideData true, the data will be merged with the existing data in the form state. * @default true */ - options?: { mergeOverrideData?: boolean }, + options?: { + /** + * If provided, will use this instead of of derived data from the current form state. + */ + data?: Data + mergeOverrideData?: boolean + }, ) => FormData | Promise export type GetFields = () => FormState @@ -175,7 +184,8 @@ export type ADD_ROW = { } export type MERGE_SERVER_STATE = { - acceptValues?: boolean + acceptValues?: AcceptValues + formStateAtTimeOfRequest?: FormState prevStateRef: React.RefObject serverState: FormState type: 'MERGE_SERVER_STATE' diff --git a/packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts b/packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts index af4f358572..caceed53f5 100644 --- a/packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts +++ b/packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts @@ -1,9 +1,6 @@ import type { - ArrayField, - BlocksField, BuildFormStateArgs, ClientFieldSchemaMap, - CollapsedPreferences, Data, DocumentPreferences, Field, @@ -153,6 +150,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom } = args if (!args.clientFieldSchemaMap && args.renderFieldFn) { + // eslint-disable-next-line no-console console.warn( 'clientFieldSchemaMap is not passed to addFieldStatePromise - this will reduce performance', ) diff --git a/test/form-state/int.spec.ts b/test/form-state/int.spec.ts index 3b1b4a896d..a417b41f86 100644 --- a/test/form-state/int.spec.ts +++ b/test/form-state/int.spec.ts @@ -565,4 +565,109 @@ describe('Form State', () => { expect(newState === currentState).toBe(true) }) + + it('should accept all values from the server regardless of local modifications, e.g. on submit', () => { + const currentState = { + title: { + value: 'Test Post (modified on the client)', + initialValue: 'Test Post', + valid: true, + passesCondition: true, + }, + computedTitle: { + value: 'Test Post (computed on the client)', + initialValue: 'Test Post', + valid: true, + passesCondition: true, + }, + } + + const formStateAtTimeOfRequest = { + ...currentState, + title: { + value: 'Test Post (modified on the client 2)', + initialValue: 'Test Post', + valid: true, + passesCondition: true, + }, + } + + const incomingStateFromServer = { + title: { + value: 'Test Post (modified on the server)', + initialValue: 'Test Post', + valid: true, + passesCondition: true, + }, + computedTitle: { + value: 'Test Post (computed on the server)', + initialValue: 'Test Post', + valid: true, + passesCondition: true, + }, + } + + const newState = mergeServerFormState({ + acceptValues: true, + currentState, + formStateAtTimeOfRequest, + incomingState: incomingStateFromServer, + }) + + expect(newState).toStrictEqual(incomingStateFromServer) + }) + + it('should not accept values from the server if they have been modified locally since the request was made, e.g. on autosave', () => { + const currentState = { + title: { + value: 'Test Post (modified on the client 1)', + initialValue: 'Test Post', + valid: true, + passesCondition: true, + }, + computedTitle: { + value: 'Test Post', + initialValue: 'Test Post', + valid: true, + passesCondition: true, + }, + } + + const formStateAtTimeOfRequest = { + ...currentState, + title: { + value: 'Test Post (modified on the client 2)', + initialValue: 'Test Post', + valid: true, + passesCondition: true, + }, + } + + const incomingStateFromServer = { + title: { + value: 'Test Post (modified on the server)', + initialValue: 'Test Post', + valid: true, + passesCondition: true, + }, + computedTitle: { + value: 'Test Post (modified on the server)', + initialValue: 'Test Post', + valid: true, + passesCondition: true, + }, + } + + const newState = mergeServerFormState({ + acceptValues: { overrideLocalChanges: false }, + currentState, + formStateAtTimeOfRequest, + incomingState: incomingStateFromServer, + }) + + expect(newState).toStrictEqual({ + ...currentState, + computedTitle: incomingStateFromServer.computedTitle, // This field was not modified locally, so should be updated from the server + }) + }) }) diff --git a/test/versions/collections/Autosave.ts b/test/versions/collections/Autosave.ts index 7350ff2b87..fff4062231 100644 --- a/test/versions/collections/Autosave.ts +++ b/test/versions/collections/Autosave.ts @@ -16,7 +16,7 @@ const AutosavePosts: CollectionConfig = { maxPerDoc: 35, drafts: { autosave: { - interval: 2000, + interval: 100, }, schedulePublish: true, }, @@ -67,6 +67,16 @@ const AutosavePosts: CollectionConfig = { type: 'textarea', required: true, }, + { + name: 'array', + type: 'array', + fields: [ + { + name: 'text', + type: 'text', + }, + ], + }, ], } diff --git a/test/versions/e2e.spec.ts b/test/versions/e2e.spec.ts index 8973c23714..adacab3212 100644 --- a/test/versions/e2e.spec.ts +++ b/test/versions/e2e.spec.ts @@ -1286,12 +1286,31 @@ describe('Versions', () => { page.removeListener('dialog', acceptAlert) }) - test('- with autosave - applies afterChange hooks to form state after autosave runs', async () => { + test('- with autosave - applies field hooks to form state after autosave runs', async () => { const url = new AdminUrlUtil(serverURL, autosaveCollectionSlug) await page.goto(url.create) const titleField = page.locator('#field-title') await titleField.fill('Initial') + await waitForAutoSaveToRunAndComplete(page) + + const computedTitleField = page.locator('#field-computedTitle') + await expect(computedTitleField).toHaveValue('Initial') + }) + + test('- with autosave - does not override local changes to form state after autosave runs', async () => { + const url = new AdminUrlUtil(serverURL, autosaveCollectionSlug) + await page.goto(url.create) + const titleField = page.locator('#field-title') + + // press slower than the autosave interval, but not faster than the response and processing + await titleField.pressSequentially('Initial', { + delay: 150, + }) + + await waitForAutoSaveToRunAndComplete(page) + + await expect(titleField).toHaveValue('Initial') const computedTitleField = page.locator('#field-computedTitle') await expect(computedTitleField).toHaveValue('Initial') }) diff --git a/test/versions/payload-types.ts b/test/versions/payload-types.ts index d393912cf4..5090bd68d7 100644 --- a/test/versions/payload-types.ts +++ b/test/versions/payload-types.ts @@ -199,6 +199,12 @@ export interface AutosavePost { title: string; computedTitle?: string | null; description: string; + array?: + | { + text?: string | null; + id?: string | null; + }[] + | null; updatedAt: string; createdAt: string; _status?: ('draft' | 'published') | null; @@ -788,6 +794,12 @@ export interface AutosavePostsSelect { title?: T; computedTitle?: T; description?: T; + array?: + | T + | { + text?: T; + id?: T; + }; updatedAt?: T; createdAt?: T; _status?: T; From 255bba9606507cc8da8df524ed3f4a26160240c6 Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Tue, 12 Aug 2025 14:59:06 -0400 Subject: [PATCH 10/14] feat(ui): update query presets ux (#13095) Surfaces query preset controls more prominently. Query presets are central to the function of the list view, if enabled, but the UI is easily overlooked. This also sets the stage for future enhancements, such as pinned presets, etc. Also improves the usability of the search field by extending the hitbox of the input fully to the boundaries of the container. Before: https://github.com/user-attachments/assets/3203561c-68cc-43f4-8ded-c51b7c8e8f0c After: https://github.com/user-attachments/assets/13dce7c9-67d8-471f-a85c-2795938b3e3e --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1210743577153864 --------- Co-authored-by: Dan Ribbens --- packages/translations/src/clientKeys.ts | 2 + packages/translations/src/languages/ar.ts | 2 + packages/translations/src/languages/az.ts | 2 + packages/translations/src/languages/bg.ts | 2 + packages/translations/src/languages/bnBd.ts | 2 + packages/translations/src/languages/bnIn.ts | 2 + packages/translations/src/languages/ca.ts | 2 + packages/translations/src/languages/cs.ts | 2 + packages/translations/src/languages/da.ts | 2 + packages/translations/src/languages/de.ts | 2 + packages/translations/src/languages/en.ts | 2 + packages/translations/src/languages/es.ts | 2 + packages/translations/src/languages/et.ts | 2 + packages/translations/src/languages/fa.ts | 2 + packages/translations/src/languages/fr.ts | 2 + packages/translations/src/languages/he.ts | 2 + packages/translations/src/languages/hr.ts | 2 + packages/translations/src/languages/hu.ts | 2 + packages/translations/src/languages/hy.ts | 2 + packages/translations/src/languages/id.ts | 2 + packages/translations/src/languages/it.ts | 2 + packages/translations/src/languages/ja.ts | 2 + packages/translations/src/languages/ko.ts | 2 + packages/translations/src/languages/lt.ts | 2 + packages/translations/src/languages/lv.ts | 2 + packages/translations/src/languages/my.ts | 2 + packages/translations/src/languages/nb.ts | 2 + packages/translations/src/languages/nl.ts | 2 + packages/translations/src/languages/pl.ts | 2 + packages/translations/src/languages/pt.ts | 2 + packages/translations/src/languages/ro.ts | 2 + packages/translations/src/languages/rs.ts | 2 + .../translations/src/languages/rsLatin.ts | 2 + packages/translations/src/languages/ru.ts | 2 + packages/translations/src/languages/sk.ts | 2 + packages/translations/src/languages/sl.ts | 2 + packages/translations/src/languages/sv.ts | 2 + packages/translations/src/languages/th.ts | 2 + packages/translations/src/languages/tr.ts | 2 + packages/translations/src/languages/uk.ts | 2 + packages/translations/src/languages/vi.ts | 2 + packages/translations/src/languages/zh.ts | 2 + packages/translations/src/languages/zhTw.ts | 2 + .../ListControls/ActiveQueryPreset/index.scss | 33 -- .../ui/src/elements/ListControls/index.scss | 76 +++-- .../ui/src/elements/ListControls/index.tsx | 288 ++++++++---------- packages/ui/src/elements/Pill/index.scss | 2 +- .../QueryPresets/QueryPresetBar/index.scss | 41 +++ .../QueryPresetBar/index.tsx} | 201 ++++++------ .../QueryPresetToggler/index.scss | 61 ++++ .../QueryPresetToggler}/index.tsx | 58 ++-- test/query-presets/e2e.spec.ts | 101 ++---- tools/constants/src/index.js | 14 + 53 files changed, 516 insertions(+), 445 deletions(-) delete mode 100644 packages/ui/src/elements/ListControls/ActiveQueryPreset/index.scss create mode 100644 packages/ui/src/elements/QueryPresets/QueryPresetBar/index.scss rename packages/ui/src/elements/{ListControls/useQueryPresets.tsx => QueryPresets/QueryPresetBar/index.tsx} (63%) create mode 100644 packages/ui/src/elements/QueryPresets/QueryPresetToggler/index.scss rename packages/ui/src/elements/{ListControls/ActiveQueryPreset => QueryPresets/QueryPresetToggler}/index.tsx (53%) create mode 100644 tools/constants/src/index.js diff --git a/packages/translations/src/clientKeys.ts b/packages/translations/src/clientKeys.ts index 67c99862e4..2723986f76 100644 --- a/packages/translations/src/clientKeys.ts +++ b/packages/translations/src/clientKeys.ts @@ -217,6 +217,7 @@ export const clientTranslationKeys = createClientTranslationKeys([ 'general:deleted', 'general:deletedAt', 'general:deletePermanently', + 'general:deleteLabel', 'general:deletedSuccessfully', 'general:deletedCountSuccessfully', 'general:deleting', @@ -274,6 +275,7 @@ export const clientTranslationKeys = createClientTranslationKeys([ 'general:movingCount', 'general:name', 'general:next', + 'general:newLabel', 'general:noDateSelected', 'general:noFiltersSet', 'general:noLabel', diff --git a/packages/translations/src/languages/ar.ts b/packages/translations/src/languages/ar.ts index e58e1589df..2d6027a171 100644 --- a/packages/translations/src/languages/ar.ts +++ b/packages/translations/src/languages/ar.ts @@ -276,6 +276,7 @@ export const arTranslations: DefaultTranslationsObject = { deletedAt: 'تم الحذف في', deletedCountSuccessfully: 'تمّ حذف {{count}} {{label}} بنجاح.', deletedSuccessfully: 'تمّ الحذف بنجاح.', + deleteLabel: 'احذف {{label}}', deletePermanently: 'تجاوز السلة واحذف بشكل دائم', deleting: 'يتمّ الحذف...', depth: 'عمق', @@ -335,6 +336,7 @@ export const arTranslations: DefaultTranslationsObject = { moveUp: 'التّحريك إلى الأعلى', moving: 'التحرك', movingCount: 'نقل {{count}} {{label}}', + newLabel: 'جديد {{label}}', newPassword: 'كلمة مرور جديدة', next: 'التالي', no: 'لا', diff --git a/packages/translations/src/languages/az.ts b/packages/translations/src/languages/az.ts index 08b1f07359..ef6b5ceb9f 100644 --- a/packages/translations/src/languages/az.ts +++ b/packages/translations/src/languages/az.ts @@ -288,6 +288,7 @@ export const azTranslations: DefaultTranslationsObject = { deletedAt: 'Silinib Tarixi', deletedCountSuccessfully: '{{count}} {{label}} uğurla silindi.', deletedSuccessfully: 'Uğurla silindi.', + deleteLabel: '{{label}} silin', deletePermanently: 'Çöplüyü atlayın və daimi olaraq silin', deleting: 'Silinir...', depth: 'Dərinlik', @@ -348,6 +349,7 @@ export const azTranslations: DefaultTranslationsObject = { moveUp: 'Yuxarı hərəkət et', moving: 'Hərəkət edir', movingCount: '{{count}} {{label}} köçürülür', + newLabel: 'Yeni {{label}}', newPassword: 'Yeni şifrə', next: 'Növbəti', no: 'Xeyr', diff --git a/packages/translations/src/languages/bg.ts b/packages/translations/src/languages/bg.ts index a92388bb4c..f30557fc0a 100644 --- a/packages/translations/src/languages/bg.ts +++ b/packages/translations/src/languages/bg.ts @@ -285,6 +285,7 @@ export const bgTranslations: DefaultTranslationsObject = { deletedAt: 'Изтрито на', deletedCountSuccessfully: 'Изтрити {{count}} {{label}} успешно.', deletedSuccessfully: 'Изтрито успешно.', + deleteLabel: 'Изтрий {{label}}', deletePermanently: 'Пропуснете кошчето и изтрийте перманентно', deleting: 'Изтриване...', depth: 'Дълбочина', @@ -345,6 +346,7 @@ export const bgTranslations: DefaultTranslationsObject = { moveUp: 'Нагоре', moving: 'Преместване', movingCount: 'Преместване на {{count}} {{label}}', + newLabel: 'Нов {{label}}', newPassword: 'Нова парола', next: 'Следващ', no: 'Не', diff --git a/packages/translations/src/languages/bnBd.ts b/packages/translations/src/languages/bnBd.ts index 01276ef13c..a4fe7e6a14 100644 --- a/packages/translations/src/languages/bnBd.ts +++ b/packages/translations/src/languages/bnBd.ts @@ -290,6 +290,7 @@ export const bnBdTranslations: DefaultTranslationsObject = { deletedAt: 'মুছে ফেলার সময়', deletedCountSuccessfully: '{{count}} {{label}} সফলভাবে মুছে ফেলা হয়েছে।', deletedSuccessfully: 'সফলভাবে মুছে ফেলা হয়েছে।', + deleteLabel: '{{label}} মুছে ফেলুন', deletePermanently: 'ট্র্যাশ এড়িয়ে স্থায়ীভাবে মুছুন', deleting: 'মুছে ফেলা হচ্ছে...', depth: 'গভীরতা', @@ -350,6 +351,7 @@ export const bnBdTranslations: DefaultTranslationsObject = { moveUp: 'উপরে সরান', moving: 'স্থানান্তর করা হচ্ছে', movingCount: '{{count}} {{label}} স্থানান্তর করা হচ্ছে', + newLabel: 'নতুন {{label}}', newPassword: 'নতুন পাসওয়ার্ড', next: 'পরবর্তী', no: 'না', diff --git a/packages/translations/src/languages/bnIn.ts b/packages/translations/src/languages/bnIn.ts index 3d0efea0ab..0f305b87b9 100644 --- a/packages/translations/src/languages/bnIn.ts +++ b/packages/translations/src/languages/bnIn.ts @@ -289,6 +289,7 @@ export const bnInTranslations: DefaultTranslationsObject = { deletedAt: 'মুছে ফেলার সময়', deletedCountSuccessfully: '{{count}} {{label}} সফলভাবে মুছে ফেলা হয়েছে।', deletedSuccessfully: 'সফলভাবে মুছে ফেলা হয়েছে।', + deleteLabel: '{{label}} মুছে ফেলুন', deletePermanently: 'ট্র্যাশ এড়িয়ে চিরতরে মুছে ফেলুন', deleting: 'মুছে ফেলা হচ্ছে...', depth: 'গভীরতা', @@ -349,6 +350,7 @@ export const bnInTranslations: DefaultTranslationsObject = { moveUp: 'উপরে সরান', moving: 'স্থানান্তর করা হচ্ছে', movingCount: '{{count}} {{label}} স্থানান্তর করা হচ্ছে', + newLabel: 'নতুন {{label}}', newPassword: 'নতুন পাসওয়ার্ড', next: 'পরবর্তী', no: 'না', diff --git a/packages/translations/src/languages/ca.ts b/packages/translations/src/languages/ca.ts index e340d98eec..143c1768bf 100644 --- a/packages/translations/src/languages/ca.ts +++ b/packages/translations/src/languages/ca.ts @@ -287,6 +287,7 @@ export const caTranslations: DefaultTranslationsObject = { deletedAt: 'Eliminat en', deletedCountSuccessfully: 'Eliminat {{count}} {{label}} correctament.', deletedSuccessfully: 'Eliminat correntament.', + deleteLabel: 'Esborra {{label}}', deletePermanently: 'Omet la paperera i elimina permanentment', deleting: 'Eliminant...', depth: 'Profunditat', @@ -347,6 +348,7 @@ export const caTranslations: DefaultTranslationsObject = { moveUp: 'Move amunt', moving: 'En moviment', movingCount: 'Moure {{count}} {{label}}', + newLabel: 'Nou {{label}}', newPassword: 'Nova contrasenya', next: 'Seguent', no: 'No', diff --git a/packages/translations/src/languages/cs.ts b/packages/translations/src/languages/cs.ts index d475579f31..85e8ba3009 100644 --- a/packages/translations/src/languages/cs.ts +++ b/packages/translations/src/languages/cs.ts @@ -284,6 +284,7 @@ export const csTranslations: DefaultTranslationsObject = { deletedAt: 'Smazáno dne', deletedCountSuccessfully: 'Úspěšně smazáno {{count}} {{label}}.', deletedSuccessfully: 'Úspěšně odstraněno.', + deleteLabel: 'Smazat {{label}}', deletePermanently: 'Preskočit koš a smazat trvale', deleting: 'Odstraňování...', depth: 'Hloubka', @@ -344,6 +345,7 @@ export const csTranslations: DefaultTranslationsObject = { moveUp: 'Posunout nahoru', moving: 'Přesun', movingCount: 'Přesunout {{count}} {{label}}', + newLabel: 'Nový {{label}}', newPassword: 'Nové heslo', next: 'Další', no: 'Ne', diff --git a/packages/translations/src/languages/da.ts b/packages/translations/src/languages/da.ts index 199e044f5a..bb2ea42d04 100644 --- a/packages/translations/src/languages/da.ts +++ b/packages/translations/src/languages/da.ts @@ -284,6 +284,7 @@ export const daTranslations: DefaultTranslationsObject = { deletedAt: 'Slettet Ved', deletedCountSuccessfully: 'Slettet {{count}} {{label}}.', deletedSuccessfully: 'Slettet.', + deleteLabel: 'Slet {{label}}', deletePermanently: 'Spring affald over og slet permanent', deleting: 'Sletter...', depth: 'Dybde', @@ -344,6 +345,7 @@ export const daTranslations: DefaultTranslationsObject = { moveUp: 'Ryk op', moving: 'Flytter', movingCount: 'Flytter {{count}} {{label}}', + newLabel: 'Ny {{label}}', newPassword: 'Ny adgangskode', next: 'Næste', no: 'Nej', diff --git a/packages/translations/src/languages/de.ts b/packages/translations/src/languages/de.ts index 910d99f4b9..867b8fd16b 100644 --- a/packages/translations/src/languages/de.ts +++ b/packages/translations/src/languages/de.ts @@ -295,6 +295,7 @@ export const deTranslations: DefaultTranslationsObject = { deletedAt: 'Gelöscht am', deletedCountSuccessfully: '{{count}} {{label}} erfolgreich gelöscht.', deletedSuccessfully: 'Erfolgreich gelöscht.', + deleteLabel: '{{label}} löschen', deletePermanently: 'Überspringen Sie den Papierkorb und löschen Sie dauerhaft.', deleting: 'Löschen...', depth: 'Tiefe', @@ -355,6 +356,7 @@ export const deTranslations: DefaultTranslationsObject = { moveUp: 'Nach oben bewegen', moving: 'Umziehen', movingCount: 'Verschieben {{count}} {{label}}', + newLabel: 'Neu {{label}}', newPassword: 'Neues Passwort', next: 'Nächste', no: 'Nein', diff --git a/packages/translations/src/languages/en.ts b/packages/translations/src/languages/en.ts index 2cacb46e5c..6cb2f90ac0 100644 --- a/packages/translations/src/languages/en.ts +++ b/packages/translations/src/languages/en.ts @@ -289,6 +289,7 @@ export const enTranslations = { deletedAt: 'Deleted At', deletedCountSuccessfully: 'Deleted {{count}} {{label}} successfully.', deletedSuccessfully: 'Deleted successfully.', + deleteLabel: 'Delete {{label}}', deletePermanently: 'Skip trash and delete permanently', deleting: 'Deleting...', depth: 'Depth', @@ -349,6 +350,7 @@ export const enTranslations = { moveUp: 'Move Up', moving: 'Moving', movingCount: 'Moving {{count}} {{label}}', + newLabel: 'New {{label}}', newPassword: 'New Password', next: 'Next', no: 'No', diff --git a/packages/translations/src/languages/es.ts b/packages/translations/src/languages/es.ts index 8ca8c5b504..e47f9ff0b8 100644 --- a/packages/translations/src/languages/es.ts +++ b/packages/translations/src/languages/es.ts @@ -291,6 +291,7 @@ export const esTranslations: DefaultTranslationsObject = { deletedAt: 'Eliminado En', deletedCountSuccessfully: 'Se eliminaron {{count}} {{label}} correctamente.', deletedSuccessfully: 'Eliminado correctamente.', + deleteLabel: 'Eliminar {{label}}', deletePermanently: 'Omitir la papelera y eliminar permanentemente', deleting: 'Eliminando...', depth: 'Profundidad', @@ -351,6 +352,7 @@ export const esTranslations: DefaultTranslationsObject = { moveUp: 'Mover arriba', moving: 'Moviendo', movingCount: 'Moviendo {{count}} {{label}}', + newLabel: 'Nuevo {{label}}', newPassword: 'Nueva contraseña', next: 'Siguiente', no: 'No', diff --git a/packages/translations/src/languages/et.ts b/packages/translations/src/languages/et.ts index 0a7aaa3991..799c1536ef 100644 --- a/packages/translations/src/languages/et.ts +++ b/packages/translations/src/languages/et.ts @@ -283,6 +283,7 @@ export const etTranslations: DefaultTranslationsObject = { deletedAt: 'Kustutatud', deletedCountSuccessfully: 'Kustutatud {{count}} {{label}} edukalt.', deletedSuccessfully: 'Kustutatud edukalt.', + deleteLabel: 'Kustuta {{label}}', deletePermanently: 'Jäta prügikasti vahele ja kustuta lõplikult', deleting: 'Kustutamine...', depth: 'Sügavus', @@ -342,6 +343,7 @@ export const etTranslations: DefaultTranslationsObject = { moveUp: 'Liiguta üles', moving: 'Liikumine', movingCount: 'Liigutan {{count}} {{label}}', + newLabel: 'Uus {{label}}', newPassword: 'Uus parool', next: 'Järgmine', no: 'Ei', diff --git a/packages/translations/src/languages/fa.ts b/packages/translations/src/languages/fa.ts index 12237ecff8..70482e12e2 100644 --- a/packages/translations/src/languages/fa.ts +++ b/packages/translations/src/languages/fa.ts @@ -282,6 +282,7 @@ export const faTranslations: DefaultTranslationsObject = { deletedAt: 'حذف شده در', deletedCountSuccessfully: 'تعداد {{count}} {{label}} با موفقیت پاک گردید.', deletedSuccessfully: 'با موفقیت حذف شد.', + deleteLabel: 'حذف {{label}}', deletePermanently: 'پرش از سطل زباله و حذف دائمی', deleting: 'در حال حذف...', depth: 'عمق', @@ -342,6 +343,7 @@ export const faTranslations: DefaultTranslationsObject = { moveUp: 'حرکت به بالا', moving: 'در حال حرکت', movingCount: 'انتقال {{count}} {{label}}', + newLabel: 'جدید {{label}}', newPassword: 'گذرواژه تازه', next: 'بعدی', no: 'نه', diff --git a/packages/translations/src/languages/fr.ts b/packages/translations/src/languages/fr.ts index 1c908c850a..b4fceb4c80 100644 --- a/packages/translations/src/languages/fr.ts +++ b/packages/translations/src/languages/fr.ts @@ -296,6 +296,7 @@ export const frTranslations: DefaultTranslationsObject = { deletedAt: 'Supprimé à', deletedCountSuccessfully: '{{count}} {{label}} supprimé avec succès.', deletedSuccessfully: 'Supprimé(e) avec succès.', + deleteLabel: 'Supprimer {{label}}', deletePermanently: 'Ignorer la corbeille et supprimer définitivement', deleting: 'Suppression en cours...', depth: 'Profondeur', @@ -356,6 +357,7 @@ export const frTranslations: DefaultTranslationsObject = { moveUp: 'Déplacer vers le haut', moving: 'Déménagement', movingCount: 'Déplacement de {{count}} {{label}}', + newLabel: 'Nouveau {{label}}', newPassword: 'Nouveau mot de passe', next: 'Prochain', no: 'Non', diff --git a/packages/translations/src/languages/he.ts b/packages/translations/src/languages/he.ts index dc372ad11d..26ec76214f 100644 --- a/packages/translations/src/languages/he.ts +++ b/packages/translations/src/languages/he.ts @@ -276,6 +276,7 @@ export const heTranslations: DefaultTranslationsObject = { deletedAt: 'נמחק ב', deletedCountSuccessfully: 'נמחקו {{count}} {{label}} בהצלחה.', deletedSuccessfully: 'נמחק בהצלחה.', + deleteLabel: 'מחק {{label}}', deletePermanently: 'דלג על פח האשפה ומחק לצמיתות', deleting: 'מוחק...', depth: 'עומק', @@ -335,6 +336,7 @@ export const heTranslations: DefaultTranslationsObject = { moveUp: 'הזז למעלה', moving: 'מזיז', movingCount: 'מזיז {{count}} {{label}}', + newLabel: 'חדש {{label}}', newPassword: 'סיסמה חדשה', next: 'הבא', no: 'לא', diff --git a/packages/translations/src/languages/hr.ts b/packages/translations/src/languages/hr.ts index 0276b3c5fd..cb3da0cf9e 100644 --- a/packages/translations/src/languages/hr.ts +++ b/packages/translations/src/languages/hr.ts @@ -286,6 +286,7 @@ export const hrTranslations: DefaultTranslationsObject = { deletedAt: 'Izbrisano U', deletedCountSuccessfully: 'Uspješno izbrisano {{count}} {{label}}.', deletedSuccessfully: 'Uspješno izbrisano.', + deleteLabel: 'Izbriši {{label}}', deletePermanently: 'Preskoči koš i trajno izbriši', deleting: 'Brisanje...', depth: 'Dubina', @@ -346,6 +347,7 @@ export const hrTranslations: DefaultTranslationsObject = { moveUp: 'Pomakni gore', moving: 'Pomicanje', movingCount: 'Pomicanje {{count}} {{label}}', + newLabel: 'Novi {{label}}', newPassword: 'Nova lozinka', next: 'Sljedeće', no: 'Ne', diff --git a/packages/translations/src/languages/hu.ts b/packages/translations/src/languages/hu.ts index a7d7d37d73..a185794cd3 100644 --- a/packages/translations/src/languages/hu.ts +++ b/packages/translations/src/languages/hu.ts @@ -290,6 +290,7 @@ export const huTranslations: DefaultTranslationsObject = { deletedAt: 'Törölve Ekkor', deletedCountSuccessfully: '{{count}} {{label}} sikeresen törölve.', deletedSuccessfully: 'Sikeresen törölve.', + deleteLabel: 'Törölje a {{label}}-t', deletePermanently: 'Hagyja ki a kukát és törölje véglegesen', deleting: 'Törlés...', depth: 'Mélység', @@ -349,6 +350,7 @@ export const huTranslations: DefaultTranslationsObject = { moveUp: 'Mozgatás felfelé', moving: 'Költözés', movingCount: '{{Count}} {{label}} mozgatása', + newLabel: 'Új {{label}}', newPassword: 'Új jelszó', next: 'Következő', no: 'Nem', diff --git a/packages/translations/src/languages/hy.ts b/packages/translations/src/languages/hy.ts index 4b3843940c..56a8da7e22 100644 --- a/packages/translations/src/languages/hy.ts +++ b/packages/translations/src/languages/hy.ts @@ -287,6 +287,7 @@ export const hyTranslations: DefaultTranslationsObject = { deletedAt: 'Ջնջված է', deletedCountSuccessfully: '{{count}} {{label}} հաջողությամբ ջնջված է։', deletedSuccessfully: 'Հաջողությամբ ջնջված է։', + deleteLabel: 'Ջնջել {{label}}', deletePermanently: 'Բաց թողեք աղբատուփը և հեռացրեք հավերժ:', deleting: 'Ջնջվում է...', depth: 'Խորություն', @@ -347,6 +348,7 @@ export const hyTranslations: DefaultTranslationsObject = { moveUp: 'Տեղափոխել վերև', moving: 'Տեղափոխվում', movingCount: 'Տեղափոխվում է {{count}} {{label}}', + newLabel: 'Նոր {{label}}', newPassword: 'Նոր գաղտնաբառ', next: 'Հաջորդ', no: 'Ոչ', diff --git a/packages/translations/src/languages/id.ts b/packages/translations/src/languages/id.ts index 4d14a50f44..f0beef58dc 100644 --- a/packages/translations/src/languages/id.ts +++ b/packages/translations/src/languages/id.ts @@ -290,6 +290,7 @@ export const idTranslations = { deletedAt: 'Dihapus Pada', deletedCountSuccessfully: 'Berhasil menghapus {{count}} {{label}}.', deletedSuccessfully: 'Berhasil dihapus.', + deleteLabel: 'Hapus {{label}}', deletePermanently: 'Lewati tempat sampah dan hapus secara permanen', deleting: 'Menghapus...', depth: 'Kedalaman', @@ -350,6 +351,7 @@ export const idTranslations = { moveUp: 'Pindah ke Atas', moving: 'Memindahkan', movingCount: 'Memindahkan {{count}} {{label}}', + newLabel: '', newPassword: 'Kata Sandi Baru', next: 'Berikutnya', no: 'Tidak', diff --git a/packages/translations/src/languages/it.ts b/packages/translations/src/languages/it.ts index af0c399628..ec8ef646ea 100644 --- a/packages/translations/src/languages/it.ts +++ b/packages/translations/src/languages/it.ts @@ -289,6 +289,7 @@ export const itTranslations: DefaultTranslationsObject = { deletedAt: 'Cancellato Alle', deletedCountSuccessfully: '{{count}} {{label}} eliminato con successo.', deletedSuccessfully: 'Eliminato con successo.', + deleteLabel: 'Elimina {{label}}', deletePermanently: 'Salta il cestino ed elimina definitivamente', deleting: 'Sto eliminando...', depth: 'Profondità', @@ -348,6 +349,7 @@ export const itTranslations: DefaultTranslationsObject = { moveUp: 'Sposta sopra', moving: 'In movimento', movingCount: 'Spostando {{count}} {{label}}', + newLabel: 'Nuovo {{label}}', newPassword: 'Nuova Password', next: 'Successivo', no: 'No', diff --git a/packages/translations/src/languages/ja.ts b/packages/translations/src/languages/ja.ts index d4cab9756f..29afa0a7d6 100644 --- a/packages/translations/src/languages/ja.ts +++ b/packages/translations/src/languages/ja.ts @@ -288,6 +288,7 @@ export const jaTranslations: DefaultTranslationsObject = { deletedAt: '削除された時間', deletedCountSuccessfully: '{{count}}つの{{label}}を正常に削除しました。', deletedSuccessfully: '正常に削除されました。', + deleteLabel: '{{label}}を削除します。', deletePermanently: 'ゴミ箱をスキップして完全に削除します', deleting: '削除しています...', depth: '深さ', @@ -348,6 +349,7 @@ export const jaTranslations: DefaultTranslationsObject = { moveUp: '上へ移動', moving: '移動中', movingCount: '{{count}} {{label}}を移動します', + newLabel: '新しい {{label}}', newPassword: '新しいパスワード', next: '次', no: 'いいえ', diff --git a/packages/translations/src/languages/ko.ts b/packages/translations/src/languages/ko.ts index 0823560246..9ee6018272 100644 --- a/packages/translations/src/languages/ko.ts +++ b/packages/translations/src/languages/ko.ts @@ -283,6 +283,7 @@ export const koTranslations: DefaultTranslationsObject = { deletedAt: '삭제된 시간', deletedCountSuccessfully: '{{count}}개의 {{label}}를 삭제했습니다.', deletedSuccessfully: '삭제되었습니다.', + deleteLabel: '{{label}} 삭제', deletePermanently: '휴지통 건너뛰고 영구적으로 삭제하세요', deleting: '삭제 중...', depth: '깊이', @@ -343,6 +344,7 @@ export const koTranslations: DefaultTranslationsObject = { moveUp: '위로 이동', moving: '이동하는', movingCount: '{{count}} {{label}}을(를) 이동시킵니다.', + newLabel: '새로운 {{label}}', newPassword: '새 비밀번호', next: '다음', no: '아니요', diff --git a/packages/translations/src/languages/lt.ts b/packages/translations/src/languages/lt.ts index fc2e26bb13..2d0306b1e9 100644 --- a/packages/translations/src/languages/lt.ts +++ b/packages/translations/src/languages/lt.ts @@ -289,6 +289,7 @@ export const ltTranslations: DefaultTranslationsObject = { deletedAt: 'Ištrinta', deletedCountSuccessfully: 'Sėkmingai ištrinta {{count}} {{label}}.', deletedSuccessfully: 'Sėkmingai ištrinta.', + deleteLabel: 'Ištrinti {{label}}', deletePermanently: 'Praleiskite šiukšliadėžę ir ištrinkite visam laikui', deleting: 'Trinama...', depth: 'Gylis', @@ -349,6 +350,7 @@ export const ltTranslations: DefaultTranslationsObject = { moveUp: 'Pakilti', moving: 'Keliauja', movingCount: 'Perkeliama {{count}} {{label}}', + newLabel: 'Naujas {{label}}', newPassword: 'Naujas slaptažodis', next: 'Toliau', no: 'Ne', diff --git a/packages/translations/src/languages/lv.ts b/packages/translations/src/languages/lv.ts index 44a903d0ed..73a7b3b46f 100644 --- a/packages/translations/src/languages/lv.ts +++ b/packages/translations/src/languages/lv.ts @@ -287,6 +287,7 @@ export const lvTranslations: DefaultTranslationsObject = { deletedAt: 'Dzēsts datumā', deletedCountSuccessfully: 'Veiksmīgi izdzēsti {{count}} {{label}}.', deletedSuccessfully: 'Veiksmīgi izdzēsts.', + deleteLabel: 'Dzēst {{label}}', deletePermanently: 'Izlaidiet miskasti un dzēsiet neatgriezeniski', deleting: 'Dzēš...', depth: 'Dziļums', @@ -347,6 +348,7 @@ export const lvTranslations: DefaultTranslationsObject = { moveUp: 'Pārvietot uz augšu', moving: 'Pārvietojas', movingCount: 'Pārvietojot {{count}} {{label}}', + newLabel: 'Jauns {{label}}', newPassword: 'Jauna parole', next: 'Nākamais', no: 'Nē', diff --git a/packages/translations/src/languages/my.ts b/packages/translations/src/languages/my.ts index 4374f169c0..7cc7112f90 100644 --- a/packages/translations/src/languages/my.ts +++ b/packages/translations/src/languages/my.ts @@ -288,6 +288,7 @@ export const myTranslations: DefaultTranslationsObject = { deletedAt: 'Dihapus Pada', deletedCountSuccessfully: '{{count}} {{label}} ကို အောင်မြင်စွာ ဖျက်လိုက်ပါပြီ။', deletedSuccessfully: 'အောင်မြင်စွာ ဖျက်လိုက်ပါပြီ။', + deleteLabel: 'Padam {{label}}', deletePermanently: 'Langkau sampah dan padam secara kekal', deleting: 'ဖျက်နေဆဲ ...', depth: 'ထိုင်းအောက်မှု', @@ -348,6 +349,7 @@ export const myTranslations: DefaultTranslationsObject = { moveUp: 'Move Up', moving: 'ရွှေ့ပြောင်းခြင်း', movingCount: 'Memindahkan {{count}} {{label}}', + newLabel: 'Baru {{label}}', newPassword: 'စကားဝှက် အသစ်', next: 'Seterusnya', no: 'Tidak', diff --git a/packages/translations/src/languages/nb.ts b/packages/translations/src/languages/nb.ts index 4c2ef03204..d7ac550287 100644 --- a/packages/translations/src/languages/nb.ts +++ b/packages/translations/src/languages/nb.ts @@ -285,6 +285,7 @@ export const nbTranslations: DefaultTranslationsObject = { deletedAt: 'Slettet kl.', deletedCountSuccessfully: 'Slettet {{count}} {{label}}.', deletedSuccessfully: 'Slettet.', + deleteLabel: 'Slett {{label}}', deletePermanently: 'Hopp over søppel og slett permanent', deleting: 'Sletter...', depth: 'Dybde', @@ -345,6 +346,7 @@ export const nbTranslations: DefaultTranslationsObject = { moveUp: 'Flytt opp', moving: 'Flytting', movingCount: 'Flytter {{count}} {{label}}', + newLabel: 'Ny {{label}}', newPassword: 'Nytt passord', next: 'Neste', no: 'Nei', diff --git a/packages/translations/src/languages/nl.ts b/packages/translations/src/languages/nl.ts index 424d6dba4f..f88cafc2ef 100644 --- a/packages/translations/src/languages/nl.ts +++ b/packages/translations/src/languages/nl.ts @@ -293,6 +293,7 @@ export const nlTranslations: DefaultTranslationsObject = { deletedAt: 'Verwijderd Op', deletedCountSuccessfully: '{{count}} {{label}} succesvol verwijderd.', deletedSuccessfully: 'Succesvol verwijderd.', + deleteLabel: 'Verwijder {{label}}', deletePermanently: 'Overslaan prullenbak en permanent verwijderen', deleting: 'Verwijderen...', depth: 'Diepte', @@ -353,6 +354,7 @@ export const nlTranslations: DefaultTranslationsObject = { moveUp: 'Verplaats naar boven', moving: 'Verhuizen', movingCount: 'Verplaatsen {{count}} {{label}}', + newLabel: 'Nieuw {{label}}', newPassword: 'Nieuw wachtwoord', next: 'Volgende', no: 'Nee', diff --git a/packages/translations/src/languages/pl.ts b/packages/translations/src/languages/pl.ts index ee59807154..67af48c891 100644 --- a/packages/translations/src/languages/pl.ts +++ b/packages/translations/src/languages/pl.ts @@ -285,6 +285,7 @@ export const plTranslations: DefaultTranslationsObject = { deletedAt: 'Usunięto o', deletedCountSuccessfully: 'Pomyślnie usunięto {{count}} {{label}}.', deletedSuccessfully: 'Pomyślnie usunięto.', + deleteLabel: 'Usuń {{label}}', deletePermanently: 'Pomiń kosz i usuń na stałe', deleting: 'Usuwanie...', depth: 'Głębokość', @@ -345,6 +346,7 @@ export const plTranslations: DefaultTranslationsObject = { moveUp: 'Przesuń wyżej', moving: 'Przeprowadzka', movingCount: 'Przenoszenie {{count}} {{label}}', + newLabel: 'Nowy {{label}}', newPassword: 'Nowe hasło', next: 'Następny', no: 'Nie', diff --git a/packages/translations/src/languages/pt.ts b/packages/translations/src/languages/pt.ts index ca01657d8d..74ac0fc0c8 100644 --- a/packages/translations/src/languages/pt.ts +++ b/packages/translations/src/languages/pt.ts @@ -287,6 +287,7 @@ export const ptTranslations: DefaultTranslationsObject = { deletedAt: 'Excluído Em', deletedCountSuccessfully: 'Excluído {{count}} {{label}} com sucesso.', deletedSuccessfully: 'Apagado com sucesso.', + deleteLabel: 'Apagar {{label}}', deletePermanently: 'Pular lixeira e excluir permanentemente', deleting: 'Excluindo...', depth: 'Profundidade', @@ -347,6 +348,7 @@ export const ptTranslations: DefaultTranslationsObject = { moveUp: 'Mover para Cima', moving: 'Mudando', movingCount: 'Movendo {{count}} {{label}}', + newLabel: 'Novo {{label}}', newPassword: 'Nova Senha', next: 'Próximo', no: 'Não', diff --git a/packages/translations/src/languages/ro.ts b/packages/translations/src/languages/ro.ts index 9b559551bf..5ec84a4f19 100644 --- a/packages/translations/src/languages/ro.ts +++ b/packages/translations/src/languages/ro.ts @@ -291,6 +291,7 @@ export const roTranslations: DefaultTranslationsObject = { deletedAt: 'Șters la', deletedCountSuccessfully: 'Șterse cu succes {{count}} {{label}}.', deletedSuccessfully: 'Șters cu succes.', + deleteLabel: 'Șterge {{label}}', deletePermanently: 'Omite coșul și șterge definitiv', deleting: 'Deleting...', depth: 'Adâncime', @@ -351,6 +352,7 @@ export const roTranslations: DefaultTranslationsObject = { moveUp: 'Mutați în sus', moving: 'În mișcare', movingCount: 'Mutarea {{count}} {{eticheta}}', + newLabel: 'Nou {{label}}', newPassword: 'Parolă nouă', next: 'Următorul', no: 'Nu', diff --git a/packages/translations/src/languages/rs.ts b/packages/translations/src/languages/rs.ts index 449cf51b8c..1321fa5ce5 100644 --- a/packages/translations/src/languages/rs.ts +++ b/packages/translations/src/languages/rs.ts @@ -287,6 +287,7 @@ export const rsTranslations: DefaultTranslationsObject = { deletedAt: 'Obrisano u', deletedCountSuccessfully: 'Успешно избрисано {{count}} {{label}}.', deletedSuccessfully: 'Успешно избрисано.', + deleteLabel: 'Izbriši {{label}}', deletePermanently: 'Preskoči otpad i trajno izbriši', deleting: 'Брисање...', depth: 'Dubina', @@ -347,6 +348,7 @@ export const rsTranslations: DefaultTranslationsObject = { moveUp: 'Помери горе', moving: 'Pomeranje', movingCount: 'Pomeranje {{count}} {{label}}', + newLabel: 'Novi {{label}}', newPassword: 'Нова лозинка', next: 'Следећи', no: 'Не', diff --git a/packages/translations/src/languages/rsLatin.ts b/packages/translations/src/languages/rsLatin.ts index 4d0b9ca22a..826668806e 100644 --- a/packages/translations/src/languages/rsLatin.ts +++ b/packages/translations/src/languages/rsLatin.ts @@ -287,6 +287,7 @@ export const rsLatinTranslations: DefaultTranslationsObject = { deletedAt: 'Obrisano U', deletedCountSuccessfully: 'Uspešno izbrisano {{count}} {{label}}.', deletedSuccessfully: 'Uspešno izbrisano.', + deleteLabel: 'Obriši {{label}}', deletePermanently: 'Preskoči kantu za smeće i trajno izbriši', deleting: 'Brisanje...', depth: 'Dubina', @@ -347,6 +348,7 @@ export const rsLatinTranslations: DefaultTranslationsObject = { moveUp: 'Pomeri gore', moving: 'Pomeranje', movingCount: 'Pomeranje {{count}} {{label}}', + newLabel: 'Novi {{label}}', newPassword: 'Nova lozinka', next: 'Sledeći', no: 'Ne', diff --git a/packages/translations/src/languages/ru.ts b/packages/translations/src/languages/ru.ts index a50df7f591..61f3adbdf5 100644 --- a/packages/translations/src/languages/ru.ts +++ b/packages/translations/src/languages/ru.ts @@ -288,6 +288,7 @@ export const ruTranslations: DefaultTranslationsObject = { deletedAt: 'Удалено В', deletedCountSuccessfully: 'Удалено {{count}} {{label}} успешно.', deletedSuccessfully: 'Удален успешно.', + deleteLabel: 'Удалить {{label}}', deletePermanently: 'Пропустить корзину и удалить навсегда', deleting: 'Удаление...', depth: 'Глубина', @@ -348,6 +349,7 @@ export const ruTranslations: DefaultTranslationsObject = { moveUp: 'Сдвинуть вверх', moving: 'Переезд', movingCount: 'Перемещение {{count}} {{label}}', + newLabel: 'Новый {{label}}', newPassword: 'Новый пароль', next: 'Следующий', no: 'Нет', diff --git a/packages/translations/src/languages/sk.ts b/packages/translations/src/languages/sk.ts index 43fbe8b04f..cb61acbb10 100644 --- a/packages/translations/src/languages/sk.ts +++ b/packages/translations/src/languages/sk.ts @@ -287,6 +287,7 @@ export const skTranslations: DefaultTranslationsObject = { deletedAt: 'Vymazané dňa', deletedCountSuccessfully: 'Úspešne zmazané {{count}} {{label}}.', deletedSuccessfully: 'Úspešne odstránené.', + deleteLabel: 'Vymazať {{label}}', deletePermanently: 'Preskočiť kôš a odstrániť natrvalo', deleting: 'Odstraňovanie...', depth: 'Hĺbka', @@ -346,6 +347,7 @@ export const skTranslations: DefaultTranslationsObject = { moveUp: 'Presunúť hore', moving: 'Pohybujúci sa', movingCount: 'Presunutie {{count}} {{label}}', + newLabel: 'Nový {{label}}', newPassword: 'Nové heslo', next: 'Ďalej', no: 'Nie', diff --git a/packages/translations/src/languages/sl.ts b/packages/translations/src/languages/sl.ts index 5ce531d84a..db2b19eaec 100644 --- a/packages/translations/src/languages/sl.ts +++ b/packages/translations/src/languages/sl.ts @@ -285,6 +285,7 @@ export const slTranslations: DefaultTranslationsObject = { deletedAt: 'Izbrisano ob', deletedCountSuccessfully: 'Uspešno izbrisano {{count}} {{label}}.', deletedSuccessfully: 'Uspešno izbrisano.', + deleteLabel: 'Izbriši {{label}}', deletePermanently: 'Preskoči smetnjak in trajno izbriši', deleting: 'Brisanje...', depth: 'Globina', @@ -345,6 +346,7 @@ export const slTranslations: DefaultTranslationsObject = { moveUp: 'Premakni gor', moving: 'Premikanje', movingCount: 'Premikanje {{count}} {{label}}', + newLabel: 'Nov {{label}}', newPassword: 'Novo geslo', next: 'Naprej', no: 'Ne', diff --git a/packages/translations/src/languages/sv.ts b/packages/translations/src/languages/sv.ts index 860586970a..ba6f1b8325 100644 --- a/packages/translations/src/languages/sv.ts +++ b/packages/translations/src/languages/sv.ts @@ -286,6 +286,7 @@ export const svTranslations: DefaultTranslationsObject = { deletedAt: 'Raderad Vid', deletedCountSuccessfully: 'Raderade {{count}} {{label}}', deletedSuccessfully: 'Borttaget', + deleteLabel: 'Radera {{label}}', deletePermanently: 'Hoppa över papperskorgen och radera permanent', deleting: 'Tar bort...', depth: 'Djup', @@ -346,6 +347,7 @@ export const svTranslations: DefaultTranslationsObject = { moveUp: 'Flytta upp', moving: 'Flyttar', movingCount: 'Flyttar {{count}} {{label}}', + newLabel: 'Ny {{label}}', newPassword: 'Nytt lösenord', next: 'Nästa', no: 'Nej', diff --git a/packages/translations/src/languages/th.ts b/packages/translations/src/languages/th.ts index da336bc15c..28d9d8b854 100644 --- a/packages/translations/src/languages/th.ts +++ b/packages/translations/src/languages/th.ts @@ -279,6 +279,7 @@ export const thTranslations: DefaultTranslationsObject = { deletedAt: 'ถูกลบที่', deletedCountSuccessfully: 'Deleted {{count}} {{label}} successfully.', deletedSuccessfully: 'ลบสำเร็จ', + deleteLabel: 'ลบ {{label}}', deletePermanently: 'ข้ามถังขยะและลบอย่างถาวร', deleting: 'กำลังลบ...', depth: 'ความลึก', @@ -338,6 +339,7 @@ export const thTranslations: DefaultTranslationsObject = { moveUp: 'ขยับลง', moving: 'การย้ายที่อยู่', movingCount: 'ย้าย {{count}} {{label}}', + newLabel: 'ใหม่ {{label}}', newPassword: 'รหัสผ่านใหม่', next: 'ถัดไป', no: 'ไม่', diff --git a/packages/translations/src/languages/tr.ts b/packages/translations/src/languages/tr.ts index b3bb2e6923..df98a1797d 100644 --- a/packages/translations/src/languages/tr.ts +++ b/packages/translations/src/languages/tr.ts @@ -289,6 +289,7 @@ export const trTranslations: DefaultTranslationsObject = { deletedAt: 'Silindiği Tarih', deletedCountSuccessfully: '{{count}} {{label}} başarıyla silindi.', deletedSuccessfully: 'Başarıyla silindi.', + deleteLabel: "{{label}}'i sil", deletePermanently: 'Çöpü atlayın ve kalıcı olarak silin', deleting: 'Siliniyor...', depth: 'Derinlik', @@ -349,6 +350,7 @@ export const trTranslations: DefaultTranslationsObject = { moveUp: 'Yukarı taşı', moving: 'Taşınma', movingCount: '{{count}} {{label}} taşıma', + newLabel: 'Yeni {{label}}', newPassword: 'Yeni parola', next: 'Sonraki', no: 'Hayır', diff --git a/packages/translations/src/languages/uk.ts b/packages/translations/src/languages/uk.ts index 677f085495..08918c3be2 100644 --- a/packages/translations/src/languages/uk.ts +++ b/packages/translations/src/languages/uk.ts @@ -285,6 +285,7 @@ export const ukTranslations: DefaultTranslationsObject = { deletedAt: 'Видалено в', deletedCountSuccessfully: 'Успішно видалено {{count}} {{label}}.', deletedSuccessfully: 'Успішно видалено.', + deleteLabel: 'Видалити {{label}}', deletePermanently: 'Пропустити кошик та видалити назавжди', deleting: 'Видалення...', depth: 'Глибина', @@ -345,6 +346,7 @@ export const ukTranslations: DefaultTranslationsObject = { moveUp: 'Перемістити вище', moving: 'Переїзд', movingCount: 'Переміщення {{count}} {{label}}', + newLabel: 'Новий {{label}}', newPassword: 'Новий пароль', next: 'Наступний', no: 'Ні', diff --git a/packages/translations/src/languages/vi.ts b/packages/translations/src/languages/vi.ts index c0842cf49b..24b33e5924 100644 --- a/packages/translations/src/languages/vi.ts +++ b/packages/translations/src/languages/vi.ts @@ -285,6 +285,7 @@ export const viTranslations: DefaultTranslationsObject = { deletedAt: 'Đã Xóa Lúc', deletedCountSuccessfully: 'Đã xóa thành công {{count}} {{label}}.', deletedSuccessfully: 'Đã xoá thành công.', + deleteLabel: 'Xóa {{label}}', deletePermanently: 'Bỏ qua thùng rác và xóa vĩnh viễn', deleting: 'Đang xóa...', depth: 'Độ sâu', @@ -345,6 +346,7 @@ export const viTranslations: DefaultTranslationsObject = { moveUp: 'Di chuyển lên', moving: 'Di chuyển', movingCount: 'Di chuyển {{count}} {{label}}', + newLabel: 'Mới {{label}}', newPassword: 'Mật khảu mới', next: 'Tiếp theo', no: 'Không', diff --git a/packages/translations/src/languages/zh.ts b/packages/translations/src/languages/zh.ts index 2ea4fe9e3d..a4b0a68712 100644 --- a/packages/translations/src/languages/zh.ts +++ b/packages/translations/src/languages/zh.ts @@ -268,6 +268,7 @@ export const zhTranslations: DefaultTranslationsObject = { deletedAt: '已删除时间', deletedCountSuccessfully: '已成功删除 {{count}} {{label}}。', deletedSuccessfully: '已成功删除。', + deleteLabel: '删除 {{label}}', deletePermanently: '跳过垃圾箱并永久删除', deleting: '删除中...', depth: '深度', @@ -327,6 +328,7 @@ export const zhTranslations: DefaultTranslationsObject = { moveUp: '向上移动', moving: '移动', movingCount: '移动 {{count}}个{{label}}', + newLabel: '新的 {{label}}', newPassword: '新密码', next: '下一个', no: '否', diff --git a/packages/translations/src/languages/zhTw.ts b/packages/translations/src/languages/zhTw.ts index 52bd3cbf41..66cb95232d 100644 --- a/packages/translations/src/languages/zhTw.ts +++ b/packages/translations/src/languages/zhTw.ts @@ -269,6 +269,7 @@ export const zhTwTranslations: DefaultTranslationsObject = { deletedAt: '刪除時間', deletedCountSuccessfully: '已成功刪除 {{count}} 個 {{label}}。', deletedSuccessfully: '刪除成功。', + deleteLabel: '刪除 {{label}}', deletePermanently: '略過垃圾桶並永久刪除', deleting: '刪除中…', depth: '層級', @@ -328,6 +329,7 @@ export const zhTwTranslations: DefaultTranslationsObject = { moveUp: '上移', moving: '移動中', movingCount: '正在移動 {{count}} 個 {{label}}', + newLabel: '新的 {{label}}', newPassword: '新密碼', next: '下一頁', no: '否', diff --git a/packages/ui/src/elements/ListControls/ActiveQueryPreset/index.scss b/packages/ui/src/elements/ListControls/ActiveQueryPreset/index.scss deleted file mode 100644 index 3ae98af4bd..0000000000 --- a/packages/ui/src/elements/ListControls/ActiveQueryPreset/index.scss +++ /dev/null @@ -1,33 +0,0 @@ -@import '../../../scss/styles'; - -@layer payload-default { - .active-query-preset { - .pill__label { - display: flex; - gap: calc(var(--base) / 4); - display: flex; - align-items: center; - } - - &__label-text-max-width { - max-width: 100px; - overflow: hidden; - } - - &__label-text { - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - } - - &__label-and-clear-wrap { - display: flex; - align-items: center; - } - - &__clear { - display: flex; - align-items: center; - } - } -} diff --git a/packages/ui/src/elements/ListControls/index.scss b/packages/ui/src/elements/ListControls/index.scss index 86c2009f05..037080a966 100644 --- a/packages/ui/src/elements/ListControls/index.scss +++ b/packages/ui/src/elements/ListControls/index.scss @@ -2,19 +2,48 @@ @layer payload-default { .list-controls { + display: flex; + flex-direction: column; + gap: 2px; + &__wrap { display: flex; align-items: center; + justify-content: space-between; + gap: base(0.5); background-color: var(--theme-elevation-50); border-radius: var(--style-radius-m); - padding: base(0.6); - gap: base(0.6); + } + + &__search { + display: flex; + background-color: var(--theme-elevation-50); + border-radius: var(--style-radius-m); + gap: base(0.4); + flex-grow: 1; + position: relative; + + .icon { + flex-shrink: 0; + } + } + + .icon--search { + position: absolute; + left: 0; + top: 50%; + transform: translate3d(0, -50%, 0); + inset-inline-start: base(0.4); + z-index: 1; + pointer-events: none; } .search-filter { flex-grow: 1; input { + height: 46px; + padding-left: 36px; margin: 0; } } @@ -24,14 +53,11 @@ border-radius: 0; } - &__modified { - color: var(--theme-elevation-500); - } - - &__buttons-wrap { + &__buttons { display: flex; align-items: center; gap: calc(var(--base) / 4); + padding-right: 10px; } .pill-selector, @@ -43,34 +69,25 @@ @include small-break { &__wrap { - flex-wrap: wrap; - background-color: unset; - padding: 0; - position: relative; - } - - .icon--search { - position: absolute; - top: base(0.4); - inset-inline-start: base(0.4); - z-index: 1; + flex-direction: column; + align-items: stretch; + background-color: transparent; + border-radius: 0; } .search-filter { width: 100%; + input { - padding: base(0.4) base(2); + height: 40px; + padding: 0 base(1.5); } } - &__buttons-wrap { - [dir='ltr'] & { - margin-right: 0; - } - - [dir='rtl'] & { - margin-left: 0; - } + &__buttons { + padding-right: 0; + margin: 0; + width: 100%; .pill { padding: base(0.2) base(0.2) base(0.2) base(0.4); @@ -78,11 +95,6 @@ } } - &__buttons { - margin: 0; - width: 100%; - } - .pill-selector, .where-builder, .sort-complex { diff --git a/packages/ui/src/elements/ListControls/index.tsx b/packages/ui/src/elements/ListControls/index.tsx index ebdb31d6ae..37e5abe066 100644 --- a/packages/ui/src/elements/ListControls/index.tsx +++ b/packages/ui/src/elements/ListControls/index.tsx @@ -7,7 +7,7 @@ import React, { Fragment, useEffect, useRef, useState } from 'react' import type { ListControlsProps } from './types.js' -import { Popup, PopupList } from '../../elements/Popup/index.js' +import { Popup } from '../../elements/Popup/index.js' import { useUseTitleField } from '../../hooks/useUseAsTitle.js' import { ChevronIcon } from '../../icons/Chevron/index.js' import { Dots } from '../../icons/Dots/index.js' @@ -18,11 +18,10 @@ import { AnimateHeight } from '../AnimateHeight/index.js' import { ColumnSelector } from '../ColumnSelector/index.js' import { GroupByBuilder } from '../GroupByBuilder/index.js' import { Pill } from '../Pill/index.js' +import { QueryPresetBar } from '../QueryPresets/QueryPresetBar/index.js' import { SearchFilter } from '../SearchFilter/index.js' import { WhereBuilder } from '../WhereBuilder/index.js' -import { ActiveQueryPreset } from './ActiveQueryPreset/index.js' import { getTextFieldsToBeSearched } from './getTextFieldsToBeSearched.js' -import { useQueryPresets } from './useQueryPresets.js' import './index.scss' const baseClass = 'list-controls' @@ -41,7 +40,7 @@ export const ListControls: React.FC = (props) => { enableColumns = true, enableFilters = true, enableSort = false, - listMenuItems: listMenuItemsFromProps, + listMenuItems, queryPreset: activePreset, queryPresetPermissions, renderedFilters, @@ -50,21 +49,6 @@ export const ListControls: React.FC = (props) => { const { handleSearchChange, query } = useListQuery() - const { - CreateNewPresetDrawer, - DeletePresetModal, - EditPresetDrawer, - hasModifiedPreset, - openPresetListDrawer, - PresetListDrawer, - queryPresetMenuItems, - resetPreset, - } = useQueryPresets({ - activePreset, - collectionSlug, - queryPresetPermissions, - }) - const titleField = useUseTitleField(collectionConfig) const { i18n, t } = useTranslation() @@ -139,25 +123,17 @@ export const ListControls: React.FC = (props) => { } }, [t, listSearchableFields, i18n, searchLabel]) - let listMenuItems: React.ReactNode[] = listMenuItemsFromProps - - if ( - collectionConfig.enableQueryPresets && - !disableQueryPresets && - queryPresetMenuItems?.length > 0 - ) { - // Cannot push or unshift into `listMenuItemsFromProps` as it will mutate the original array - listMenuItems = [ - ...queryPresetMenuItems, - listMenuItemsFromProps?.length > 0 ? : null, - ...(listMenuItemsFromProps || []), - ] - } - return ( - -
-
+
+ {collectionConfig?.enableQueryPresets && !disableQueryPresets && ( + + )} +
+
= (props) => { label={searchLabelTranslated.current} searchQueryParam={query?.search} /> - {activePreset && hasModifiedPreset ? ( -
Modified
- ) : null} -
-
- {!smallBreak && {beforeActions && beforeActions}} - {enableColumns && ( - } - id="toggle-columns" - onClick={() => - setVisibleDrawer(visibleDrawer !== 'columns' ? 'columns' : undefined) - } - pillStyle="light" - size="small" - > - {t('general:columns')} - - )} - {enableFilters && ( - } - id="toggle-list-filters" - onClick={() => setVisibleDrawer(visibleDrawer !== 'where' ? 'where' : undefined)} - pillStyle="light" - size="small" - > - {t('general:filters')} - - )} - {enableSort && ( - } - onClick={() => setVisibleDrawer(visibleDrawer !== 'sort' ? 'sort' : undefined)} - pillStyle="light" - size="small" - > - {t('general:sort')} - - )} - {!disableQueryPresets && ( - - )} - {collectionConfig.admin.groupBy && ( - } - id="toggle-group-by" - onClick={() => - setVisibleDrawer(visibleDrawer !== 'group-by' ? 'group-by' : undefined) - } - pillStyle="light" - size="small" - > - {t('general:groupByLabel', { - label: '', - })} - - )} - {listMenuItems && Array.isArray(listMenuItems) && listMenuItems.length > 0 && ( - } - className={`${baseClass}__popup`} - horizontalAlign="right" - id="list-menu" - size="medium" - verticalAlign="bottom" - > - {listMenuItems.map((item, i) => ( - {item} - ))} - - )} -
-
- {enableColumns && ( - - - - )} - - - - {collectionConfig.admin.groupBy && ( - - - - )} +
+ {!smallBreak && {beforeActions && beforeActions}} + {enableColumns && ( + } + id="toggle-list-columns" + onClick={() => setVisibleDrawer(visibleDrawer !== 'columns' ? 'columns' : undefined)} + pillStyle="light" + size="small" + > + {t('general:columns')} + + )} + {enableFilters && ( + } + id="toggle-list-filters" + onClick={() => setVisibleDrawer(visibleDrawer !== 'where' ? 'where' : undefined)} + pillStyle="light" + size="small" + > + {t('general:filters')} + + )} + {enableSort && ( + } + id="toggle-list-sort" + onClick={() => setVisibleDrawer(visibleDrawer !== 'sort' ? 'sort' : undefined)} + pillStyle="light" + size="small" + > + {t('general:sort')} + + )} + {collectionConfig.admin.groupBy && ( + } + id="toggle-group-by" + onClick={() => + setVisibleDrawer(visibleDrawer !== 'group-by' ? 'group-by' : undefined) + } + pillStyle="light" + size="small" + > + {t('general:groupByLabel', { + label: '', + })} + + )} + {listMenuItems && Array.isArray(listMenuItems) && listMenuItems.length > 0 && ( + } + className={`${baseClass}__popup`} + horizontalAlign="right" + id="list-menu" + size="small" + verticalAlign="bottom" + > + {listMenuItems.map((item, i) => ( + {item} + ))} + + )} +
- {PresetListDrawer} - {EditPresetDrawer} - {CreateNewPresetDrawer} - {DeletePresetModal} - + {enableColumns && ( + + + + )} + + + + {collectionConfig.admin.groupBy && ( + + + + )} +
) } diff --git a/packages/ui/src/elements/Pill/index.scss b/packages/ui/src/elements/Pill/index.scss index 7f071efe15..e29e6555a8 100644 --- a/packages/ui/src/elements/Pill/index.scss +++ b/packages/ui/src/elements/Pill/index.scss @@ -39,7 +39,7 @@ outline-offset: var(--accessibility-outline-offset); } - .icon { + &__icon .icon { flex-shrink: 0; width: var(--pill-icon-size); height: var(--pill-icon-size); diff --git a/packages/ui/src/elements/QueryPresets/QueryPresetBar/index.scss b/packages/ui/src/elements/QueryPresets/QueryPresetBar/index.scss new file mode 100644 index 0000000000..0481f8c00f --- /dev/null +++ b/packages/ui/src/elements/QueryPresets/QueryPresetBar/index.scss @@ -0,0 +1,41 @@ +@import '../../../scss/styles'; + +@layer payload-default { + .query-preset-bar { + display: flex; + gap: base(0.5); + justify-content: space-between; + background-color: var(--theme-elevation-50); + border-radius: var(--style-radius-m); + padding: base(0.5); + + &__menu { + display: flex; + align-items: center; + gap: 4px; + flex-grow: 1; + } + + &__create-new-preset { + height: 100%; + padding: 0 3px; + background: transparent; + box-shadow: inset 0 0 0 1px var(--theme-elevation-150); + + &:hover { + background: transparent; + } + } + + &__menu-items { + overflow: auto; + display: flex; + gap: base(0.5); + + button { + color: var(--theme-elevation-500); + margin: 0; + } + } + } +} diff --git a/packages/ui/src/elements/ListControls/useQueryPresets.tsx b/packages/ui/src/elements/QueryPresets/QueryPresetBar/index.tsx similarity index 63% rename from packages/ui/src/elements/ListControls/useQueryPresets.tsx rename to packages/ui/src/elements/QueryPresets/QueryPresetBar/index.tsx index 587eac6784..15ce25060f 100644 --- a/packages/ui/src/elements/ListControls/useQueryPresets.tsx +++ b/packages/ui/src/elements/QueryPresets/QueryPresetBar/index.tsx @@ -1,43 +1,35 @@ -import type { CollectionSlug, QueryPreset, SanitizedCollectionPermission } from 'payload' +import type { QueryPreset, SanitizedCollectionPermission } from 'payload' import { useModal } from '@faceless-ui/modal' import { getTranslation } from '@payloadcms/translations' import { transformColumnsToPreferences, transformColumnsToSearchParams } from 'payload/shared' -import React, { useCallback, useMemo } from 'react' +import React, { Fragment, useCallback, useMemo } from 'react' import { toast } from 'sonner' -import { useConfig } from '../../providers/Config/index.js' -import { useListQuery } from '../../providers/ListQuery/context.js' -import { useTranslation } from '../../providers/Translation/index.js' -import { ConfirmationModal } from '../ConfirmationModal/index.js' -import { useDocumentDrawer } from '../DocumentDrawer/index.js' -import { useListDrawer } from '../ListDrawer/index.js' -import { PopupList } from '../Popup/index.js' -import { PopupListGroupLabel } from '../Popup/PopupGroupLabel/index.js' -import { Translation } from '../Translation/index.js' +import { PlusIcon } from '../../../icons/Plus/index.js' +import { useConfig } from '../../../providers/Config/index.js' +import { useListQuery } from '../../../providers/ListQuery/context.js' +import { useTranslation } from '../../../providers/Translation/index.js' +import { ConfirmationModal } from '../../ConfirmationModal/index.js' +import { useDocumentDrawer } from '../../DocumentDrawer/index.js' +import { useListDrawer } from '../../ListDrawer/index.js' +import { ListSelectionButton } from '../../ListSelection/index.js' +import { Pill } from '../../Pill/index.js' +import { Translation } from '../../Translation/index.js' +import { QueryPresetToggler } from '../QueryPresetToggler/index.js' +import './index.scss' const confirmDeletePresetModalSlug = 'confirm-delete-preset' const queryPresetsSlug = 'payload-query-presets' -export const useQueryPresets = ({ - activePreset, - collectionSlug, - queryPresetPermissions, -}: { +const baseClass = 'query-preset-bar' + +export const QueryPresetBar: React.FC<{ activePreset: QueryPreset - collectionSlug: CollectionSlug + collectionSlug?: string queryPresetPermissions: SanitizedCollectionPermission -}): { - CreateNewPresetDrawer: React.ReactNode - DeletePresetModal: React.ReactNode - EditPresetDrawer: React.ReactNode - hasModifiedPreset: boolean - openPresetListDrawer: () => void - PresetListDrawer: React.ReactNode - queryPresetMenuItems: React.ReactNode[] - resetPreset: () => Promise -} => { +}> = ({ activePreset, collectionSlug, queryPresetPermissions }) => { const { modified, query, refineListData, setModified: setQueryModified } = useListQuery() const { i18n, t } = useTranslation() @@ -193,80 +185,81 @@ export const useQueryPresets = ({ setQueryModified, ]) - // Memoize so that components aren't re-rendered on query and column changes - const queryPresetMenuItems = useMemo(() => { - const hasModifiedPreset = activePreset && modified + const hasModifiedPreset = activePreset && modified - return [ - , - - {hasModifiedPreset && ( - { - await refineListData( - { - columns: transformColumnsToSearchParams(activePreset.columns), - where: activePreset.where, - }, - false, - ) + return ( + +
+
+ + } + id="create-new-preset" + onClick={() => { + openCreateNewDrawer() }} - > - {t('general:reset')} - - )} - {hasModifiedPreset && queryPresetPermissions.update && ( - { - await saveCurrentChanges() - }} - > - {activePreset?.isShared ? t('general:updateForEveryone') : t('general:save')} - - )} - { - openCreateNewDrawer() - }} - > - {t('general:createNew')} - - {activePreset && queryPresetPermissions?.delete && ( - <> - openModal(confirmDeletePresetModalSlug)}> - {t('general:delete')} - - { - openDocumentDrawer() + size="small" + /> +
+
+ {hasModifiedPreset && ( + { + await refineListData( + { + columns: transformColumnsToSearchParams(activePreset.columns), + where: activePreset.where, + }, + false, + ) }} + type="button" > - {t('general:edit')} - - - )} - , - ] - }, [ - activePreset, - queryPresetPermissions?.delete, - queryPresetPermissions?.update, - openCreateNewDrawer, - openDocumentDrawer, - openModal, - saveCurrentChanges, - t, - refineListData, - modified, - presetConfig?.labels?.plural, - i18n, - ]) - - return { - CreateNewPresetDrawer: ( + {t('general:reset')} + + )} + {hasModifiedPreset && queryPresetPermissions.update && ( + { + await saveCurrentChanges() + }} + type="button" + > + {activePreset?.isShared ? t('general:updateForEveryone') : t('fields:saveChanges')} + + )} + {activePreset && queryPresetPermissions?.delete && ( + + openModal(confirmDeletePresetModalSlug)} + type="button" + > + {t('general:deleteLabel', { label: presetConfig?.labels?.singular })} + + { + openDocumentDrawer() + }} + type="button" + > + {t('general:editLabel', { label: presetConfig?.labels?.singular })} + + + )} +
+
- ), - DeletePresetModal: ( - ), - EditPresetDrawer: ( { // setSelectedPreset(undefined) @@ -313,10 +302,6 @@ export const useQueryPresets = ({ await handlePresetChange(doc as QueryPreset) }} /> - ), - hasModifiedPreset: modified, - openPresetListDrawer: openListDrawer, - PresetListDrawer: ( - ), - queryPresetMenuItems, - resetPreset: resetQueryPreset, - } +
+ ) } diff --git a/packages/ui/src/elements/QueryPresets/QueryPresetToggler/index.scss b/packages/ui/src/elements/QueryPresets/QueryPresetToggler/index.scss new file mode 100644 index 0000000000..8dbe8c6c25 --- /dev/null +++ b/packages/ui/src/elements/QueryPresets/QueryPresetToggler/index.scss @@ -0,0 +1,61 @@ +@import '../../../scss/styles'; + +@layer payload-default { + .active-query-preset { + &__label { + display: flex; + align-items: center; + } + + &__label-text-max-width { + max-width: 100px; + overflow: hidden; + } + + &__label-text { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + &__label-and-clear-wrap { + display: flex; + align-items: center; + } + + &__shared { + margin-right: 2px; + } + + &__clear { + display: flex; + align-items: center; + width: var(--pill-icon-size); + height: var(--pill-icon-size); + } + + &--active { + box-shadow: inset 0 0 0 1px var(--theme-elevation-200); + background-color: var(--theme-elevation-0); + padding-right: 4px; + + &:hover { + background-color: var(--theme-elevation-100); + } + } + } + + html[data-theme='dark'] { + .active-query-preset { + &--active { + box-shadow: inset 0 0 0 1px var(--theme-elevation-300); + color: var(--theme-elevation-0); + background-color: var(--theme-elevation-800); + + &:hover { + background-color: var(--theme-elevation-700); + } + } + } + } +} diff --git a/packages/ui/src/elements/ListControls/ActiveQueryPreset/index.tsx b/packages/ui/src/elements/QueryPresets/QueryPresetToggler/index.tsx similarity index 53% rename from packages/ui/src/elements/ListControls/ActiveQueryPreset/index.tsx rename to packages/ui/src/elements/QueryPresets/QueryPresetToggler/index.tsx index 803b40c78f..08f7977abd 100644 --- a/packages/ui/src/elements/ListControls/ActiveQueryPreset/index.tsx +++ b/packages/ui/src/elements/QueryPresets/QueryPresetToggler/index.tsx @@ -12,7 +12,7 @@ import './index.scss' const baseClass = 'active-query-preset' -export function ActiveQueryPreset({ +export function QueryPresetToggler({ activePreset, openPresetListDrawer, resetPreset, @@ -35,38 +35,40 @@ export function ActiveQueryPreset({ onClick={() => { openPresetListDrawer() }} - pillStyle={activePreset ? 'always-white' : 'light'} + pillStyle="light" size="small" > - {activePreset?.isShared && } -
-
- {activePreset?.title || - t('general:selectLabel', { - label: getTranslation(presetsConfig.labels.singular, i18n), - })} +
+ {activePreset?.isShared && } +
+
+ {activePreset?.title || + t('general:selectLabel', { + label: getTranslation(presetsConfig.labels.singular, i18n), + })} +
-
- {activePreset ? ( -
{ - e.stopPropagation() - await resetPreset() - }} - onKeyDown={async (e) => { - if (e.key === 'Enter' || e.key === ' ') { + {activePreset ? ( +
{ e.stopPropagation() await resetPreset() - } - }} - role="button" - tabIndex={0} - > - -
- ) : null} + }} + onKeyDown={async (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.stopPropagation() + await resetPreset() + } + }} + role="button" + tabIndex={0} + > + +
+ ) : null} +
) } diff --git a/test/query-presets/e2e.spec.ts b/test/query-presets/e2e.spec.ts index ff1921734c..0e16604018 100644 --- a/test/query-presets/e2e.spec.ts +++ b/test/query-presets/e2e.spec.ts @@ -19,7 +19,6 @@ import { // throttleTest, } from '../helpers.js' import { AdminUrlUtil } from '../helpers/adminUrlUtil.js' -import { clickListMenuItem, openListMenu } from '../helpers/e2e/toggleListMenu.js' import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js' import { TEST_TIMEOUT_LONG } from '../playwright.config.js' import { assertURLParams } from './helpers/assertURLParams.js' @@ -190,9 +189,8 @@ describe('Query Presets', () => { test('should delete a preset, clear selection, and reset changes', async () => { await page.goto(pagesUrl.list) await selectPreset({ page, presetTitle: seededData.everyone.title }) - await openListMenu({ page }) - await clickListMenuItem({ page, menuItemLabel: 'Delete' }) + await page.locator('#delete-preset').click() await page.locator('#confirm-delete-preset #confirm-action').click() @@ -249,75 +247,29 @@ describe('Query Presets', () => { test('should only show "edit" and "delete" controls when there is an active preset', async () => { await page.goto(pagesUrl.list) - await openListMenu({ page }) - - await expect( - page.locator('#list-menu .popup__content .popup-button-list__button', { - hasText: exactText('Edit'), - }), - ).toBeHidden() - - await expect( - page.locator('#list-menu .popup__content .popup-button-list__button', { - hasText: exactText('Delete'), - }), - ).toBeHidden() - + await expect(page.locator('#edit-preset')).toBeHidden() + await expect(page.locator('#delete-preset')).toBeHidden() await selectPreset({ page, presetTitle: seededData.everyone.title }) - - await openListMenu({ page }) - - await expect( - page.locator('#list-menu .popup__content .popup-button-list__button', { - hasText: exactText('Edit'), - }), - ).toBeVisible() - - await expect( - page.locator('#list-menu .popup__content .popup-button-list__button', { - hasText: exactText('Delete'), - }), - ).toBeVisible() + await expect(page.locator('#edit-preset')).toBeVisible() + await expect(page.locator('#delete-preset')).toBeVisible() }) test('should only show "reset" and "save" controls when there is an active preset and changes have been made', async () => { await page.goto(pagesUrl.list) - await openListMenu({ page }) + await expect(page.locator('#reset-preset')).toBeHidden() - await expect( - page.locator('#list-menu .popup__content .popup-button-list__button', { - hasText: exactText('Reset'), - }), - ).toBeHidden() - - await expect( - page.locator('#list-menu .popup__content .popup-button-list__button', { - hasText: exactText('Update for everyone'), - }), - ).toBeHidden() - - await expect( - page.locator('#list-menu .popup__content .popup-button-list__button', { - hasText: exactText('Save'), - }), - ).toBeHidden() + await expect(page.locator('#save-preset')).toBeHidden() await selectPreset({ page, presetTitle: seededData.onlyMe.title }) await toggleColumn(page, { columnLabel: 'ID' }) - await openListMenu({ page }) + await expect(page.locator('#reset-preset')).toBeVisible() await expect( - page.locator('#list-menu .popup__content .popup-button-list__button', { - hasText: exactText('Reset'), - }), - ).toBeVisible() - - await expect( - page.locator('#list-menu .popup__content .popup-button-list__button', { - hasText: exactText('Save'), + page.locator('#save-preset', { + hasText: exactText('Save changes'), }), ).toBeVisible() }) @@ -329,12 +281,12 @@ describe('Query Presets', () => { await toggleColumn(page, { columnLabel: 'ID' }) - await openListMenu({ page }) - // When not shared, the label is "Save" + await expect(page.locator('#save-preset')).toBeVisible() + await expect( - page.locator('#list-menu .popup__content .popup-button-list__button', { - hasText: exactText('Save'), + page.locator('#save-preset', { + hasText: exactText('Save changes'), }), ).toBeVisible() @@ -342,11 +294,9 @@ describe('Query Presets', () => { await toggleColumn(page, { columnLabel: 'ID' }) - await openListMenu({ page }) - // When shared, the label is "Update for everyone" await expect( - page.locator('#list-menu .popup__content .popup-button-list__button', { + page.locator('#save-preset', { hasText: exactText('Update for everyone'), }), ).toBeVisible() @@ -362,27 +312,28 @@ describe('Query Presets', () => { hasText: exactText('ID'), }) - await openListMenu({ page }) - await clickListMenuItem({ page, menuItemLabel: 'Reset' }) + await page.locator('#reset-preset').click() await openListColumns(page, {}) await expect(column).toHaveClass(/pill-selector__pill--selected/) }) - test('should only enter modified state when changes are made to an active preset', async () => { + test.skip('should only enter modified state when changes are made to an active preset', async () => { await page.goto(pagesUrl.list) await expect(page.locator('.list-controls__modified')).toBeHidden() await selectPreset({ page, presetTitle: seededData.everyone.title }) await expect(page.locator('.list-controls__modified')).toBeHidden() await toggleColumn(page, { columnLabel: 'ID' }) await expect(page.locator('.list-controls__modified')).toBeVisible() - await openListMenu({ page }) - await clickListMenuItem({ page, menuItemLabel: 'Update for everyone' }) + + await page.locator('#save-preset').click() + await expect(page.locator('.list-controls__modified')).toBeHidden() await toggleColumn(page, { columnLabel: 'ID' }) await expect(page.locator('.list-controls__modified')).toBeVisible() - await openListMenu({ page }) - await clickListMenuItem({ page, menuItemLabel: 'Reset' }) + + await page.locator('#reset-preset').click() + await expect(page.locator('.list-controls__modified')).toBeHidden() }) @@ -392,8 +343,7 @@ describe('Query Presets', () => { await page.goto(pagesUrl.list) await selectPreset({ page, presetTitle: seededData.everyone.title }) - await openListMenu({ page }) - await clickListMenuItem({ page, menuItemLabel: 'Edit' }) + await page.locator('#edit-preset').click() const drawer = page.locator('[id^=doc-drawer_payload-query-presets_0_]') const titleValue = drawer.locator('input[name="title"]') @@ -427,8 +377,7 @@ describe('Query Presets', () => { const presetTitle = 'New Preset' - await openListMenu({ page }) - await clickListMenuItem({ page, menuItemLabel: 'Create New' }) + await page.locator('#create-new-preset').click() const modal = page.locator('[id^=doc-drawer_payload-query-presets_0_]') await expect(modal).toBeVisible() await modal.locator('input[name="title"]').fill(presetTitle) diff --git a/tools/constants/src/index.js b/tools/constants/src/index.js new file mode 100644 index 0000000000..f87596c825 --- /dev/null +++ b/tools/constants/src/index.js @@ -0,0 +1,14 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TEMPLATES_DIR = exports.PACKAGES_DIR = exports.ROOT_PACKAGE_JSON = exports.PROJECT_ROOT = void 0; +var node_url_1 = require("node:url"); +var path_1 = require("path"); +var filename = (0, node_url_1.fileURLToPath)(import.meta.url); +var dirname = path_1.default.dirname(filename); +/** + * Path to the project root + */ +exports.PROJECT_ROOT = path_1.default.resolve(dirname, '../../../'); +exports.ROOT_PACKAGE_JSON = path_1.default.resolve(exports.PROJECT_ROOT, 'package.json'); +exports.PACKAGES_DIR = path_1.default.resolve(exports.PROJECT_ROOT, 'packages'); +exports.TEMPLATES_DIR = path_1.default.resolve(exports.PROJECT_ROOT, 'templates'); From 35ca98e70e313412d0a0ccbd3ea92a9a979b797b Mon Sep 17 00:00:00 2001 From: Jessica Rynkar <67977755+jessrynkar@users.noreply.github.com> Date: Wed, 13 Aug 2025 09:02:43 +0100 Subject: [PATCH 11/14] fix: version view breaks when tab field has function for label (#13415) ### What? Fixes an issue where using a function as the `label` for a `tabs` field causes the versions UI to break. ### Why? The versions UI was not properly resolving function labels on `tab` fields, leading to a crash when trying to render them. ### How? Tweaked the logic so that if the label is a function, it gets called before rendering. Fixes #13375 --- .../src/views/Version/RenderFieldsToDiff/buildVersionFields.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/buildVersionFields.tsx b/packages/next/src/views/Version/RenderFieldsToDiff/buildVersionFields.tsx index 950ccae89d..e76eee1ec6 100644 --- a/packages/next/src/views/Version/RenderFieldsToDiff/buildVersionFields.tsx +++ b/packages/next/src/views/Version/RenderFieldsToDiff/buildVersionFields.tsx @@ -329,7 +329,7 @@ const buildVersionField = ({ versionFromSiblingData: 'name' in tab ? valueFrom?.[tab.name] : valueFrom, versionToSiblingData: 'name' in tab ? valueTo?.[tab.name] : valueTo, }).versionFields, - label: tab.label, + label: typeof tab.label === 'function' ? tab.label({ i18n, t: i18n.t }) : tab.label, } if (tabVersion?.fields?.length) { baseVersionField.tabs.push(tabVersion) From 5a99d8c5f4de7a8ca1caf1686bc8a318eb43c8c7 Mon Sep 17 00:00:00 2001 From: Jessica Rynkar <67977755+jessrynkar@users.noreply.github.com> Date: Wed, 13 Aug 2025 12:26:59 +0100 Subject: [PATCH 12/14] fix: upload with no filename gives vague error (#13414) ### What? Adds validation to the file upload field to ensure a filename is provided. If the filename is missing, a clear error message is shown to the user instead of a general error. ### Why? Currently, attempting to upload a file without a filename results in a generic error message: `Something went wrong.` This makes it unclear for users to understand what the issue is. ### How? The upload field validation has been updated to explicitly check for a missing filename. If the filename is undefined or null, the error message `A filename is required` is now shown. Fixes #13410 --- packages/ui/src/elements/Upload/index.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/ui/src/elements/Upload/index.tsx b/packages/ui/src/elements/Upload/index.tsx index 475a9efd28..37ff56fccb 100644 --- a/packages/ui/src/elements/Upload/index.tsx +++ b/packages/ui/src/elements/Upload/index.tsx @@ -34,6 +34,10 @@ const validate = (value) => { return 'A file is required.' } + if (value && (!value.name || value.name === '')) { + return 'A file name is required.' + } + return true } From 0688050eb6f253c953f0b58f360179469497655b Mon Sep 17 00:00:00 2001 From: Elliot DeNolf Date: Wed, 13 Aug 2025 09:20:13 -0400 Subject: [PATCH 13/14] chore(release): v3.51.0 [skip ci] --- package.json | 2 +- packages/admin-bar/package.json | 2 +- packages/create-payload-app/package.json | 2 +- packages/db-mongodb/package.json | 2 +- packages/db-postgres/package.json | 2 +- packages/db-sqlite/package.json | 2 +- packages/db-vercel-postgres/package.json | 2 +- packages/drizzle/package.json | 2 +- packages/email-nodemailer/package.json | 2 +- packages/email-resend/package.json | 2 +- packages/graphql/package.json | 2 +- packages/live-preview-react/package.json | 2 +- packages/live-preview-vue/package.json | 2 +- packages/live-preview/package.json | 2 +- packages/next/package.json | 2 +- packages/payload-cloud/package.json | 2 +- packages/payload/package.json | 2 +- packages/plugin-cloud-storage/package.json | 2 +- packages/plugin-form-builder/package.json | 2 +- packages/plugin-import-export/package.json | 2 +- packages/plugin-multi-tenant/package.json | 2 +- packages/plugin-nested-docs/package.json | 2 +- packages/plugin-redirects/package.json | 2 +- packages/plugin-search/package.json | 2 +- packages/plugin-sentry/package.json | 2 +- packages/plugin-seo/package.json | 2 +- packages/plugin-stripe/package.json | 2 +- packages/richtext-lexical/package.json | 2 +- packages/richtext-slate/package.json | 2 +- packages/storage-azure/package.json | 2 +- packages/storage-gcs/package.json | 2 +- packages/storage-s3/package.json | 2 +- packages/storage-uploadthing/package.json | 2 +- packages/storage-vercel-blob/package.json | 2 +- packages/translations/package.json | 2 +- packages/ui/package.json | 2 +- 36 files changed, 36 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index b17e512594..4e138abb3e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "payload-monorepo", - "version": "3.50.0", + "version": "3.51.0", "private": true, "type": "module", "workspaces": [ diff --git a/packages/admin-bar/package.json b/packages/admin-bar/package.json index 8745c6e3fe..8b374a9d66 100644 --- a/packages/admin-bar/package.json +++ b/packages/admin-bar/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/admin-bar", - "version": "3.50.0", + "version": "3.51.0", "description": "An admin bar for React apps using Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/create-payload-app/package.json b/packages/create-payload-app/package.json index 1e3096d6a1..24fd90cd22 100644 --- a/packages/create-payload-app/package.json +++ b/packages/create-payload-app/package.json @@ -1,6 +1,6 @@ { "name": "create-payload-app", - "version": "3.50.0", + "version": "3.51.0", "homepage": "https://payloadcms.com", "repository": { "type": "git", diff --git a/packages/db-mongodb/package.json b/packages/db-mongodb/package.json index da2b23257b..1ff95c20c9 100644 --- a/packages/db-mongodb/package.json +++ b/packages/db-mongodb/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/db-mongodb", - "version": "3.50.0", + "version": "3.51.0", "description": "The officially supported MongoDB database adapter for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/db-postgres/package.json b/packages/db-postgres/package.json index ac4da28599..ff07660e97 100644 --- a/packages/db-postgres/package.json +++ b/packages/db-postgres/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/db-postgres", - "version": "3.50.0", + "version": "3.51.0", "description": "The officially supported Postgres database adapter for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/db-sqlite/package.json b/packages/db-sqlite/package.json index 5e28d12fff..1c2a418dc8 100644 --- a/packages/db-sqlite/package.json +++ b/packages/db-sqlite/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/db-sqlite", - "version": "3.50.0", + "version": "3.51.0", "description": "The officially supported SQLite database adapter for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/db-vercel-postgres/package.json b/packages/db-vercel-postgres/package.json index d8fa3dd596..16782cebb7 100644 --- a/packages/db-vercel-postgres/package.json +++ b/packages/db-vercel-postgres/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/db-vercel-postgres", - "version": "3.50.0", + "version": "3.51.0", "description": "Vercel Postgres adapter for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/drizzle/package.json b/packages/drizzle/package.json index 1db65736ba..1baf465104 100644 --- a/packages/drizzle/package.json +++ b/packages/drizzle/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/drizzle", - "version": "3.50.0", + "version": "3.51.0", "description": "A library of shared functions used by different payload database adapters", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/email-nodemailer/package.json b/packages/email-nodemailer/package.json index 593a066a61..0286f3a5c2 100644 --- a/packages/email-nodemailer/package.json +++ b/packages/email-nodemailer/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/email-nodemailer", - "version": "3.50.0", + "version": "3.51.0", "description": "Payload Nodemailer Email Adapter", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/email-resend/package.json b/packages/email-resend/package.json index 65449ce867..d77543c766 100644 --- a/packages/email-resend/package.json +++ b/packages/email-resend/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/email-resend", - "version": "3.50.0", + "version": "3.51.0", "description": "Payload Resend Email Adapter", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/graphql/package.json b/packages/graphql/package.json index 99b9c1f173..ee6045c5e9 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/graphql", - "version": "3.50.0", + "version": "3.51.0", "homepage": "https://payloadcms.com", "repository": { "type": "git", diff --git a/packages/live-preview-react/package.json b/packages/live-preview-react/package.json index e382d93a45..209d56d647 100644 --- a/packages/live-preview-react/package.json +++ b/packages/live-preview-react/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/live-preview-react", - "version": "3.50.0", + "version": "3.51.0", "description": "The official React SDK for Payload Live Preview", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/live-preview-vue/package.json b/packages/live-preview-vue/package.json index 413af47465..9e5c20ec6a 100644 --- a/packages/live-preview-vue/package.json +++ b/packages/live-preview-vue/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/live-preview-vue", - "version": "3.50.0", + "version": "3.51.0", "description": "The official Vue SDK for Payload Live Preview", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/live-preview/package.json b/packages/live-preview/package.json index ce359471d1..275f39fd71 100644 --- a/packages/live-preview/package.json +++ b/packages/live-preview/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/live-preview", - "version": "3.50.0", + "version": "3.51.0", "description": "The official live preview JavaScript SDK for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/next/package.json b/packages/next/package.json index d8c1a06151..02ca468e11 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/next", - "version": "3.50.0", + "version": "3.51.0", "homepage": "https://payloadcms.com", "repository": { "type": "git", diff --git a/packages/payload-cloud/package.json b/packages/payload-cloud/package.json index d4fa8b3adf..985d31441a 100644 --- a/packages/payload-cloud/package.json +++ b/packages/payload-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/payload-cloud", - "version": "3.50.0", + "version": "3.51.0", "description": "The official Payload Cloud plugin", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/payload/package.json b/packages/payload/package.json index be1d6cfa88..9ac70bae9b 100644 --- a/packages/payload/package.json +++ b/packages/payload/package.json @@ -1,6 +1,6 @@ { "name": "payload", - "version": "3.50.0", + "version": "3.51.0", "description": "Node, React, Headless CMS and Application Framework built on Next.js", "keywords": [ "admin panel", diff --git a/packages/plugin-cloud-storage/package.json b/packages/plugin-cloud-storage/package.json index 76e62db230..d901efd62a 100644 --- a/packages/plugin-cloud-storage/package.json +++ b/packages/plugin-cloud-storage/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-cloud-storage", - "version": "3.50.0", + "version": "3.51.0", "description": "The official cloud storage plugin for Payload CMS", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/plugin-form-builder/package.json b/packages/plugin-form-builder/package.json index ebaaf276a0..7b10941c33 100644 --- a/packages/plugin-form-builder/package.json +++ b/packages/plugin-form-builder/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-form-builder", - "version": "3.50.0", + "version": "3.51.0", "description": "Form builder plugin for Payload CMS", "keywords": [ "payload", diff --git a/packages/plugin-import-export/package.json b/packages/plugin-import-export/package.json index ec93697c4e..b9806b50b7 100644 --- a/packages/plugin-import-export/package.json +++ b/packages/plugin-import-export/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-import-export", - "version": "3.50.0", + "version": "3.51.0", "description": "Import-Export plugin for Payload", "keywords": [ "payload", diff --git a/packages/plugin-multi-tenant/package.json b/packages/plugin-multi-tenant/package.json index d6f16fb142..72fb974c51 100644 --- a/packages/plugin-multi-tenant/package.json +++ b/packages/plugin-multi-tenant/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-multi-tenant", - "version": "3.50.0", + "version": "3.51.0", "description": "Multi Tenant plugin for Payload", "keywords": [ "payload", diff --git a/packages/plugin-nested-docs/package.json b/packages/plugin-nested-docs/package.json index 4f6ff57df2..c19a87d89a 100644 --- a/packages/plugin-nested-docs/package.json +++ b/packages/plugin-nested-docs/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-nested-docs", - "version": "3.50.0", + "version": "3.51.0", "description": "The official Nested Docs plugin for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/plugin-redirects/package.json b/packages/plugin-redirects/package.json index 0284afb521..95a95de97f 100644 --- a/packages/plugin-redirects/package.json +++ b/packages/plugin-redirects/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-redirects", - "version": "3.50.0", + "version": "3.51.0", "description": "Redirects plugin for Payload", "keywords": [ "payload", diff --git a/packages/plugin-search/package.json b/packages/plugin-search/package.json index 0d4cc43dd8..5627d51467 100644 --- a/packages/plugin-search/package.json +++ b/packages/plugin-search/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-search", - "version": "3.50.0", + "version": "3.51.0", "description": "Search plugin for Payload", "keywords": [ "payload", diff --git a/packages/plugin-sentry/package.json b/packages/plugin-sentry/package.json index bfea5ec719..6d89f42624 100644 --- a/packages/plugin-sentry/package.json +++ b/packages/plugin-sentry/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-sentry", - "version": "3.50.0", + "version": "3.51.0", "description": "Sentry plugin for Payload", "keywords": [ "payload", diff --git a/packages/plugin-seo/package.json b/packages/plugin-seo/package.json index dfc907a3fb..aac236a3b4 100644 --- a/packages/plugin-seo/package.json +++ b/packages/plugin-seo/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-seo", - "version": "3.50.0", + "version": "3.51.0", "description": "SEO plugin for Payload", "keywords": [ "payload", diff --git a/packages/plugin-stripe/package.json b/packages/plugin-stripe/package.json index 8c213e80b7..5502a69c41 100644 --- a/packages/plugin-stripe/package.json +++ b/packages/plugin-stripe/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-stripe", - "version": "3.50.0", + "version": "3.51.0", "description": "Stripe plugin for Payload", "keywords": [ "payload", diff --git a/packages/richtext-lexical/package.json b/packages/richtext-lexical/package.json index 963b3d1a08..3c04c4b993 100644 --- a/packages/richtext-lexical/package.json +++ b/packages/richtext-lexical/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/richtext-lexical", - "version": "3.50.0", + "version": "3.51.0", "description": "The officially supported Lexical richtext adapter for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/richtext-slate/package.json b/packages/richtext-slate/package.json index 319784c509..d8fe48c32b 100644 --- a/packages/richtext-slate/package.json +++ b/packages/richtext-slate/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/richtext-slate", - "version": "3.50.0", + "version": "3.51.0", "description": "The officially supported Slate richtext adapter for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/storage-azure/package.json b/packages/storage-azure/package.json index cc22394eec..e907570f40 100644 --- a/packages/storage-azure/package.json +++ b/packages/storage-azure/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/storage-azure", - "version": "3.50.0", + "version": "3.51.0", "description": "Payload storage adapter for Azure Blob Storage", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/storage-gcs/package.json b/packages/storage-gcs/package.json index bebb3a84b9..fae79f8d65 100644 --- a/packages/storage-gcs/package.json +++ b/packages/storage-gcs/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/storage-gcs", - "version": "3.50.0", + "version": "3.51.0", "description": "Payload storage adapter for Google Cloud Storage", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/storage-s3/package.json b/packages/storage-s3/package.json index 1b5929dcd5..178c145c37 100644 --- a/packages/storage-s3/package.json +++ b/packages/storage-s3/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/storage-s3", - "version": "3.50.0", + "version": "3.51.0", "description": "Payload storage adapter for Amazon S3", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/storage-uploadthing/package.json b/packages/storage-uploadthing/package.json index 83d2276881..ac7ea0026f 100644 --- a/packages/storage-uploadthing/package.json +++ b/packages/storage-uploadthing/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/storage-uploadthing", - "version": "3.50.0", + "version": "3.51.0", "description": "Payload storage adapter for uploadthing", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/storage-vercel-blob/package.json b/packages/storage-vercel-blob/package.json index 4f90450387..4153754c12 100644 --- a/packages/storage-vercel-blob/package.json +++ b/packages/storage-vercel-blob/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/storage-vercel-blob", - "version": "3.50.0", + "version": "3.51.0", "description": "Payload storage adapter for Vercel Blob Storage", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/translations/package.json b/packages/translations/package.json index 62a8230da6..0d4c3d5803 100644 --- a/packages/translations/package.json +++ b/packages/translations/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/translations", - "version": "3.50.0", + "version": "3.51.0", "homepage": "https://payloadcms.com", "repository": { "type": "git", diff --git a/packages/ui/package.json b/packages/ui/package.json index 8cd23dd5a8..9f2b25a50e 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/ui", - "version": "3.50.0", + "version": "3.51.0", "homepage": "https://payloadcms.com", "repository": { "type": "git", From 0e8a6c0162d2c07de1784fcedd4210542c2a4bec Mon Sep 17 00:00:00 2001 From: Elliot DeNolf Date: Wed, 13 Aug 2025 09:47:04 -0400 Subject: [PATCH 14/14] templates: bump for v3.51.0 (#13457) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Automated bump of templates for v3.51.0 Triggered by user: @denolfe Co-authored-by: github-actions[bot] --- templates/with-postgres/package.json | 12 ++++---- ...tial.json => 20250813_133122_initial.json} | 2 +- ..._initial.ts => 20250813_133122_initial.ts} | 0 .../with-postgres/src/migrations/index.ts | 8 ++--- templates/with-vercel-mongodb/package.json | 16 +++++----- templates/with-vercel-postgres/package.json | 16 +++++----- ...tial.json => 20250813_133039_initial.json} | 2 +- ..._initial.ts => 20250813_133039_initial.ts} | 0 .../src/migrations/index.ts | 8 ++--- templates/with-vercel-website/package.json | 30 +++++++++---------- ...tial.json => 20250813_133100_initial.json} | 2 +- ..._initial.ts => 20250813_133100_initial.ts} | 0 .../src/migrations/index.ts | 8 ++--- 13 files changed, 52 insertions(+), 52 deletions(-) rename templates/with-postgres/src/migrations/{20250726_113920_initial.json => 20250813_133122_initial.json} (99%) rename templates/with-postgres/src/migrations/{20250726_113920_initial.ts => 20250813_133122_initial.ts} (100%) rename templates/with-vercel-postgres/src/migrations/{20250726_113907_initial.json => 20250813_133039_initial.json} (99%) rename templates/with-vercel-postgres/src/migrations/{20250726_113907_initial.ts => 20250813_133039_initial.ts} (100%) rename templates/with-vercel-website/src/migrations/{20250726_113914_initial.json => 20250813_133100_initial.json} (99%) rename templates/with-vercel-website/src/migrations/{20250726_113914_initial.ts => 20250813_133100_initial.ts} (100%) diff --git a/templates/with-postgres/package.json b/templates/with-postgres/package.json index 7f3e511548..134dcacda9 100644 --- a/templates/with-postgres/package.json +++ b/templates/with-postgres/package.json @@ -19,15 +19,15 @@ "test:int": "cross-env NODE_OPTIONS=--no-deprecation vitest run --config ./vitest.config.mts" }, "dependencies": { - "@payloadcms/db-postgres": "3.49.0", - "@payloadcms/next": "3.49.0", - "@payloadcms/payload-cloud": "3.49.0", - "@payloadcms/richtext-lexical": "3.49.0", - "@payloadcms/ui": "3.49.0", + "@payloadcms/db-postgres": "3.51.0", + "@payloadcms/next": "3.51.0", + "@payloadcms/payload-cloud": "3.51.0", + "@payloadcms/richtext-lexical": "3.51.0", + "@payloadcms/ui": "3.51.0", "cross-env": "^7.0.3", "graphql": "^16.8.1", "next": "15.4.4", - "payload": "3.49.0", + "payload": "3.51.0", "react": "19.1.0", "react-dom": "19.1.0", "sharp": "0.34.2" diff --git a/templates/with-postgres/src/migrations/20250726_113920_initial.json b/templates/with-postgres/src/migrations/20250813_133122_initial.json similarity index 99% rename from templates/with-postgres/src/migrations/20250726_113920_initial.json rename to templates/with-postgres/src/migrations/20250813_133122_initial.json index 97216aac11..e2033fe78c 100644 --- a/templates/with-postgres/src/migrations/20250726_113920_initial.json +++ b/templates/with-postgres/src/migrations/20250813_133122_initial.json @@ -1,5 +1,5 @@ { - "id": "a01f43fe-f9ef-41b2-9ef2-ee199fdbf09e", + "id": "e77b65ac-57a8-4bbf-9fde-beb7803b65ca", "prevId": "00000000-0000-0000-0000-000000000000", "version": "7", "dialect": "postgresql", diff --git a/templates/with-postgres/src/migrations/20250726_113920_initial.ts b/templates/with-postgres/src/migrations/20250813_133122_initial.ts similarity index 100% rename from templates/with-postgres/src/migrations/20250726_113920_initial.ts rename to templates/with-postgres/src/migrations/20250813_133122_initial.ts diff --git a/templates/with-postgres/src/migrations/index.ts b/templates/with-postgres/src/migrations/index.ts index 88725f28dd..3d35ee5ed9 100644 --- a/templates/with-postgres/src/migrations/index.ts +++ b/templates/with-postgres/src/migrations/index.ts @@ -1,9 +1,9 @@ -import * as migration_20250726_113920_initial from './20250726_113920_initial' +import * as migration_20250813_133122_initial from './20250813_133122_initial' export const migrations = [ { - up: migration_20250726_113920_initial.up, - down: migration_20250726_113920_initial.down, - name: '20250726_113920_initial', + up: migration_20250813_133122_initial.up, + down: migration_20250813_133122_initial.down, + name: '20250813_133122_initial', }, ] diff --git a/templates/with-vercel-mongodb/package.json b/templates/with-vercel-mongodb/package.json index 6b7788acd6..5f08bc547e 100644 --- a/templates/with-vercel-mongodb/package.json +++ b/templates/with-vercel-mongodb/package.json @@ -18,16 +18,16 @@ "test:int": "cross-env NODE_OPTIONS=--no-deprecation vitest run --config ./vitest.config.mts" }, "dependencies": { - "@payloadcms/db-mongodb": "3.49.0", - "@payloadcms/next": "3.49.0", - "@payloadcms/payload-cloud": "3.49.0", - "@payloadcms/richtext-lexical": "3.49.0", - "@payloadcms/storage-vercel-blob": "3.49.0", - "@payloadcms/ui": "3.49.0", + "@payloadcms/db-mongodb": "3.51.0", + "@payloadcms/next": "3.51.0", + "@payloadcms/payload-cloud": "3.51.0", + "@payloadcms/richtext-lexical": "3.51.0", + "@payloadcms/storage-vercel-blob": "3.51.0", + "@payloadcms/ui": "3.51.0", "cross-env": "^7.0.3", "graphql": "^16.8.1", "next": "15.4.4", - "payload": "3.49.0", + "payload": "3.51.0", "react": "19.1.0", "react-dom": "19.1.0" }, @@ -49,7 +49,7 @@ "vite-tsconfig-paths": "5.1.4", "vitest": "3.2.3" }, - "packageManager": "pnpm@10.13.1", + "packageManager": "pnpm@10.14.0", "engines": { "node": "^18.20.2 || >=20.9.0" }, diff --git a/templates/with-vercel-postgres/package.json b/templates/with-vercel-postgres/package.json index 83071373bd..bc72c08c3b 100644 --- a/templates/with-vercel-postgres/package.json +++ b/templates/with-vercel-postgres/package.json @@ -19,16 +19,16 @@ "test:int": "cross-env NODE_OPTIONS=--no-deprecation vitest run --config ./vitest.config.mts" }, "dependencies": { - "@payloadcms/db-vercel-postgres": "3.49.0", - "@payloadcms/next": "3.49.0", - "@payloadcms/payload-cloud": "3.49.0", - "@payloadcms/richtext-lexical": "3.49.0", - "@payloadcms/storage-vercel-blob": "3.49.0", - "@payloadcms/ui": "3.49.0", + "@payloadcms/db-vercel-postgres": "3.51.0", + "@payloadcms/next": "3.51.0", + "@payloadcms/payload-cloud": "3.51.0", + "@payloadcms/richtext-lexical": "3.51.0", + "@payloadcms/storage-vercel-blob": "3.51.0", + "@payloadcms/ui": "3.51.0", "cross-env": "^7.0.3", "graphql": "^16.8.1", "next": "15.4.4", - "payload": "3.49.0", + "payload": "3.51.0", "react": "19.1.0", "react-dom": "19.1.0" }, @@ -50,7 +50,7 @@ "vite-tsconfig-paths": "5.1.4", "vitest": "3.2.3" }, - "packageManager": "pnpm@10.13.1", + "packageManager": "pnpm@10.14.0", "engines": { "node": "^18.20.2 || >=20.9.0" }, diff --git a/templates/with-vercel-postgres/src/migrations/20250726_113907_initial.json b/templates/with-vercel-postgres/src/migrations/20250813_133039_initial.json similarity index 99% rename from templates/with-vercel-postgres/src/migrations/20250726_113907_initial.json rename to templates/with-vercel-postgres/src/migrations/20250813_133039_initial.json index ce0a6c475f..5c6abec001 100644 --- a/templates/with-vercel-postgres/src/migrations/20250726_113907_initial.json +++ b/templates/with-vercel-postgres/src/migrations/20250813_133039_initial.json @@ -1,5 +1,5 @@ { - "id": "1da33d28-30b0-4553-8961-ddbbf2a3caa0", + "id": "5b37216d-9eee-4bb9-8603-76757ea10edc", "prevId": "00000000-0000-0000-0000-000000000000", "version": "7", "dialect": "postgresql", diff --git a/templates/with-vercel-postgres/src/migrations/20250726_113907_initial.ts b/templates/with-vercel-postgres/src/migrations/20250813_133039_initial.ts similarity index 100% rename from templates/with-vercel-postgres/src/migrations/20250726_113907_initial.ts rename to templates/with-vercel-postgres/src/migrations/20250813_133039_initial.ts diff --git a/templates/with-vercel-postgres/src/migrations/index.ts b/templates/with-vercel-postgres/src/migrations/index.ts index 0560cb28fe..a1598bb647 100644 --- a/templates/with-vercel-postgres/src/migrations/index.ts +++ b/templates/with-vercel-postgres/src/migrations/index.ts @@ -1,9 +1,9 @@ -import * as migration_20250726_113907_initial from './20250726_113907_initial' +import * as migration_20250813_133039_initial from './20250813_133039_initial' export const migrations = [ { - up: migration_20250726_113907_initial.up, - down: migration_20250726_113907_initial.down, - name: '20250726_113907_initial', + up: migration_20250813_133039_initial.up, + down: migration_20250813_133039_initial.down, + name: '20250813_133039_initial', }, ] diff --git a/templates/with-vercel-website/package.json b/templates/with-vercel-website/package.json index d114ed402d..7b68706161 100644 --- a/templates/with-vercel-website/package.json +++ b/templates/with-vercel-website/package.json @@ -23,19 +23,19 @@ "test:int": "cross-env NODE_OPTIONS=--no-deprecation vitest run --config ./vitest.config.mts" }, "dependencies": { - "@payloadcms/admin-bar": "3.49.0", - "@payloadcms/db-vercel-postgres": "3.49.0", - "@payloadcms/live-preview-react": "3.49.0", - "@payloadcms/next": "3.49.0", - "@payloadcms/payload-cloud": "3.49.0", - "@payloadcms/plugin-form-builder": "3.49.0", - "@payloadcms/plugin-nested-docs": "3.49.0", - "@payloadcms/plugin-redirects": "3.49.0", - "@payloadcms/plugin-search": "3.49.0", - "@payloadcms/plugin-seo": "3.49.0", - "@payloadcms/richtext-lexical": "3.49.0", - "@payloadcms/storage-vercel-blob": "3.49.0", - "@payloadcms/ui": "3.49.0", + "@payloadcms/admin-bar": "3.51.0", + "@payloadcms/db-vercel-postgres": "3.51.0", + "@payloadcms/live-preview-react": "3.51.0", + "@payloadcms/next": "3.51.0", + "@payloadcms/payload-cloud": "3.51.0", + "@payloadcms/plugin-form-builder": "3.51.0", + "@payloadcms/plugin-nested-docs": "3.51.0", + "@payloadcms/plugin-redirects": "3.51.0", + "@payloadcms/plugin-search": "3.51.0", + "@payloadcms/plugin-seo": "3.51.0", + "@payloadcms/richtext-lexical": "3.51.0", + "@payloadcms/storage-vercel-blob": "3.51.0", + "@payloadcms/ui": "3.51.0", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-select": "^2.0.0", @@ -49,7 +49,7 @@ "lucide-react": "^0.378.0", "next": "15.4.4", "next-sitemap": "^4.2.3", - "payload": "3.49.0", + "payload": "3.51.0", "prism-react-renderer": "^2.3.1", "react": "19.1.0", "react-dom": "19.1.0", @@ -82,7 +82,7 @@ "vite-tsconfig-paths": "5.1.4", "vitest": "3.2.3" }, - "packageManager": "pnpm@10.13.1", + "packageManager": "pnpm@10.14.0", "engines": { "node": "^18.20.2 || >=20.9.0" }, diff --git a/templates/with-vercel-website/src/migrations/20250726_113914_initial.json b/templates/with-vercel-website/src/migrations/20250813_133100_initial.json similarity index 99% rename from templates/with-vercel-website/src/migrations/20250726_113914_initial.json rename to templates/with-vercel-website/src/migrations/20250813_133100_initial.json index 7904f3baed..bb955b5b81 100644 --- a/templates/with-vercel-website/src/migrations/20250726_113914_initial.json +++ b/templates/with-vercel-website/src/migrations/20250813_133100_initial.json @@ -1,5 +1,5 @@ { - "id": "0cdb510f-c7c6-45da-8202-ffed100b9cbb", + "id": "6a069cb0-75be-477f-b47e-5d268a55acbc", "prevId": "00000000-0000-0000-0000-000000000000", "version": "7", "dialect": "postgresql", diff --git a/templates/with-vercel-website/src/migrations/20250726_113914_initial.ts b/templates/with-vercel-website/src/migrations/20250813_133100_initial.ts similarity index 100% rename from templates/with-vercel-website/src/migrations/20250726_113914_initial.ts rename to templates/with-vercel-website/src/migrations/20250813_133100_initial.ts diff --git a/templates/with-vercel-website/src/migrations/index.ts b/templates/with-vercel-website/src/migrations/index.ts index 114568d745..bf31e3ed46 100644 --- a/templates/with-vercel-website/src/migrations/index.ts +++ b/templates/with-vercel-website/src/migrations/index.ts @@ -1,9 +1,9 @@ -import * as migration_20250726_113914_initial from './20250726_113914_initial' +import * as migration_20250813_133100_initial from './20250813_133100_initial' export const migrations = [ { - up: migration_20250726_113914_initial.up, - down: migration_20250726_113914_initial.down, - name: '20250726_113914_initial', + up: migration_20250813_133100_initial.up, + down: migration_20250813_133100_initial.down, + name: '20250813_133100_initial', }, ]