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/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/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/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; + } + } + } } } 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) 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/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/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-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/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-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/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/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 749186aaa0..66cb95232d 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: '新增資料夾', @@ -267,6 +269,7 @@ export const zhTwTranslations: DefaultTranslationsObject = { deletedAt: '刪除時間', deletedCountSuccessfully: '已成功刪除 {{count}} 個 {{label}}。', deletedSuccessfully: '刪除成功。', + deleteLabel: '刪除 {{label}}', deletePermanently: '略過垃圾桶並永久刪除', deleting: '刪除中…', depth: '層級', @@ -326,6 +329,7 @@ export const zhTwTranslations: DefaultTranslationsObject = { moveUp: '上移', moving: '移動中', movingCount: '正在移動 {{count}} 個 {{label}}', + newLabel: '新的 {{label}}', newPassword: '新密碼', next: '下一頁', no: '否', @@ -390,8 +394,7 @@ export const zhTwTranslations: DefaultTranslationsObject = { success: '成功', successfullyCreated: '已成功建立 {{label}}。', successfullyDuplicated: '已成功複製 {{label}}。', - successfullyReindexed: - '已成功重新索引 {{collections}} 中 {{total}} 筆文件中的 {{count}} 筆。', + successfullyReindexed: '已成功重新索引 {{collections}} 中 {{total}} 筆文件中的 {{count}} 筆。', takeOver: '接手編輯', thisLanguage: '中文(繁體)', time: '時間', 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", diff --git a/packages/ui/src/elements/Autosave/index.tsx b/packages/ui/src/elements/Autosave/index.tsx index 2215f7e659..96b121792d 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,41 @@ 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({ + acceptValues: { + overrideLocalChanges: false, + }, + 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/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/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/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 { 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/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', }, diff --git a/packages/ui/src/elements/Upload/index.tsx b/packages/ui/src/elements/Upload/index.tsx index e9f0c92719..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 } @@ -161,7 +165,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 +353,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 +379,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 +510,7 @@ export const Upload_v4: React.FC = (props) => {
@@ -523,17 +527,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/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 79e12b29ff..aa545e4999 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,20 @@ 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 { + acceptValues = true, 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 +224,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 +233,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,26 +255,32 @@ export const Form: React.FC = (props) => { e.preventDefault() } - setProcessing(true) - setDisabled(true) + if (disableFormWhileProcessing) { + setProcessing(true) + setDisabled(true) + } if (waitForAutocomplete) { 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 @@ -290,6 +304,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')) @@ -309,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) { @@ -332,6 +341,7 @@ export const Form: React.FC = (props) => { } const formData = await contextRef.current.createFormData(overrides, { + data, mergeOverrideData: Boolean(typeof overridesFromArgs !== 'function'), }) @@ -366,25 +376,28 @@ 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({ type: 'MERGE_SERVER_STATE', - acceptValues: true, + acceptValues, + formStateAtTimeOfRequest: formStateCopy, prevStateRef: prevFormState, serverState: newFormState, }) } } + 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 +405,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 +457,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) @@ -488,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 9cfd1ef644..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 } @@ -52,7 +54,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 @@ -69,17 +71,42 @@ 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. + * 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 @@ -89,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 @@ -151,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/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/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-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-vercel-postgres/src/migrations/20250726_113907_initial.json b/templates/with-postgres/src/migrations/20250813_133122_initial.json similarity index 99% rename from templates/with-vercel-postgres/src/migrations/20250726_113907_initial.json rename to templates/with-postgres/src/migrations/20250813_133122_initial.json index ce0a6c475f..e2033fe78c 100644 --- a/templates/with-vercel-postgres/src/migrations/20250726_113907_initial.json +++ b/templates/with-postgres/src/migrations/20250813_133122_initial.json @@ -1,5 +1,5 @@ { - "id": "1da33d28-30b0-4553-8961-ddbbf2a3caa0", + "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-postgres/src/migrations/20250726_113920_initial.json b/templates/with-vercel-postgres/src/migrations/20250813_133039_initial.json similarity index 99% rename from templates/with-postgres/src/migrations/20250726_113920_initial.json rename to templates/with-vercel-postgres/src/migrations/20250813_133039_initial.json index 97216aac11..5c6abec001 100644 --- a/templates/with-postgres/src/migrations/20250726_113920_initial.json +++ b/templates/with-vercel-postgres/src/migrations/20250813_133039_initial.json @@ -1,5 +1,5 @@ { - "id": "a01f43fe-f9ef-41b2-9ef2-ee199fdbf09e", + "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/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 { 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', }, ] 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/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/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 + } }) 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: { 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/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) diff --git a/test/versions/collections/Autosave.ts b/test/versions/collections/Autosave.ts index ff18f7cb1b..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, }, @@ -53,12 +53,30 @@ const AutosavePosts: CollectionConfig = { unique: true, localized: true, }, + { + name: 'computedTitle', + label: 'Computed Title', + type: 'text', + hooks: { + beforeChange: [({ data }) => data?.title], + }, + }, { name: 'description', label: 'Description', 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 0bcf5134fa..0aa9f9fa16 100644 --- a/test/versions/e2e.spec.ts +++ b/test/versions/e2e.spec.ts @@ -1285,6 +1285,63 @@ describe('Versions', () => { // Remove listener page.removeListener('dialog', acceptAlert) }) + + 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') + }) + + 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..5090bd68d7 100644 --- a/test/versions/payload-types.ts +++ b/test/versions/payload-types.ts @@ -197,7 +197,14 @@ export interface Post { export interface AutosavePost { id: string; title: string; + computedTitle?: string | null; description: string; + array?: + | { + text?: string | null; + id?: string | null; + }[] + | null; updatedAt: string; createdAt: string; _status?: ('draft' | 'published') | null; @@ -366,7 +373,6 @@ export interface Diff { textInNamedTab1InBlock?: string | null; }; textInUnnamedTab2InBlock?: string | null; - textInUnnamedTab2InBlockAccessFalse?: string | null; id?: string | null; blockName?: string | null; blockType: 'TabsBlock'; @@ -469,7 +475,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,7 +792,14 @@ export interface PostsSelect { */ export interface AutosavePostsSelect { title?: T; + computedTitle?: T; description?: T; + array?: + | T + | { + text?: T; + id?: T; + }; updatedAt?: T; createdAt?: T; _status?: T; @@ -960,7 +972,6 @@ export interface DiffSelect { textInNamedTab1InBlock?: T; }; textInUnnamedTab2InBlock?: T; - textInUnnamedTab2InBlockAccessFalse?: T; id?: T; blockName?: T; }; @@ -995,7 +1006,6 @@ export interface DiffSelect { }; textInUnnamedTab2?: T; text?: T; - textCannotRead?: T; textArea?: T; upload?: T; uploadHasMany?: T; 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');