diff --git a/packages/ui/src/elements/PublishButton/index.tsx b/packages/ui/src/elements/PublishButton/index.tsx index d14bbfe0cb..26001be3d9 100644 --- a/packages/ui/src/elements/PublishButton/index.tsx +++ b/packages/ui/src/elements/PublishButton/index.tsx @@ -4,7 +4,7 @@ import type { PublishButtonClientProps } from 'payload' import { useModal } from '@faceless-ui/modal' import * as qs from 'qs-esm' -import React, { useCallback } from 'react' +import React, { useCallback, useEffect, useState } from 'react' import { useForm, useFormModified } from '../../forms/Form/context.js' import { FormSubmit } from '../../forms/Submit/index.js' @@ -15,6 +15,7 @@ import { useEditDepth } from '../../providers/EditDepth/index.js' import { useLocale } from '../../providers/Locale/index.js' import { useOperation } from '../../providers/Operation/index.js' import { useTranslation } from '../../providers/Translation/index.js' +import { traverseForLocalizedFields } from '../../utilities/traverseForLocalizedFields.js' import { PopupList } from '../Popup/index.js' import { ScheduleDrawer } from './ScheduleDrawer/index.js' @@ -86,6 +87,15 @@ export function PublishButton({ label: labelProp }: PublishButtonClientProps) { (hasAutosave || !modified), ) + const [hasLocalizedFields, setHasLocalizedFields] = useState(false) + + useEffect(() => { + const hasLocalizedField = traverseForLocalizedFields(entityConfig?.fields) + setHasLocalizedFields(hasLocalizedField) + }, [entityConfig?.fields]) + + const canPublishSpecificLocale = localization && hasLocalizedFields && hasPublishPermission + const operation = useOperation() const disabled = operation === 'update' && !modified @@ -213,7 +223,7 @@ export function PublishButton({ label: labelProp }: PublishButtonClientProps) { onClick={defaultPublish} size="medium" SubMenuPopupContent={ - localization || canSchedulePublish + canPublishSpecificLocale || canSchedulePublish ? ({ close }) => { return ( @@ -227,7 +237,7 @@ export function PublishButton({ label: labelProp }: PublishButtonClientProps) { )} - {localization && canPublish && ( + {canPublishSpecificLocale && ( {secondaryLabel} diff --git a/packages/ui/src/utilities/traverseForLocalizedFields.ts b/packages/ui/src/utilities/traverseForLocalizedFields.ts new file mode 100644 index 0000000000..2b801b5bed --- /dev/null +++ b/packages/ui/src/utilities/traverseForLocalizedFields.ts @@ -0,0 +1,45 @@ +import type { ClientField } from 'payload' + +export const traverseForLocalizedFields = (fields: ClientField[]): boolean => { + for (const field of fields) { + if ('localized' in field && field.localized) { + return true + } + + switch (field.type) { + case 'array': + case 'collapsible': + case 'group': + case 'row': + if (field.fields && traverseForLocalizedFields(field.fields)) { + return true + } + break + + case 'blocks': + if (field.blocks) { + for (const block of field.blocks) { + if (block.fields && traverseForLocalizedFields(block.fields)) { + return true + } + } + } + break + + case 'tabs': + if (field.tabs) { + for (const tab of field.tabs) { + if ('localized' in tab && tab.localized) { + return true + } + if ('fields' in tab && tab.fields && traverseForLocalizedFields(tab.fields)) { + return true + } + } + } + break + } + } + + return false +} diff --git a/test/localization/collections/NoLocalizedFields/index.ts b/test/localization/collections/NoLocalizedFields/index.ts index e04739bea1..7714ba10d5 100644 --- a/test/localization/collections/NoLocalizedFields/index.ts +++ b/test/localization/collections/NoLocalizedFields/index.ts @@ -4,6 +4,9 @@ export const noLocalizedFieldsCollectionSlug = 'no-localized-fields' export const NoLocalizedFieldsCollection: CollectionConfig = { slug: noLocalizedFieldsCollectionSlug, + versions: { + drafts: true, + }, fields: [ { name: 'text', diff --git a/test/localization/e2e.spec.ts b/test/localization/e2e.spec.ts index f5d4c17244..b2468ceecc 100644 --- a/test/localization/e2e.spec.ts +++ b/test/localization/e2e.spec.ts @@ -29,6 +29,7 @@ import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js' import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js' import { arrayCollectionSlug } from './collections/Array/index.js' import { nestedToArrayAndBlockCollectionSlug } from './collections/NestedToArrayAndBlock/index.js' +import { noLocalizedFieldsCollectionSlug } from './collections/NoLocalizedFields/index.js' import { richTextSlug } from './collections/RichText/index.js' import { arrayWithFallbackCollectionSlug, @@ -61,6 +62,7 @@ let urlCannotCreateDefaultLocale: AdminUrlUtil let urlPostsWithDrafts: AdminUrlUtil let urlArray: AdminUrlUtil let arrayWithFallbackURL: AdminUrlUtil +let noLocalizedFieldsURL: AdminUrlUtil const title = 'english title' const spanishTitle = 'spanish title' @@ -87,6 +89,7 @@ describe('Localization', () => { urlPostsWithDrafts = new AdminUrlUtil(serverURL, localizedDraftsSlug) urlArray = new AdminUrlUtil(serverURL, arrayCollectionSlug) arrayWithFallbackURL = new AdminUrlUtil(serverURL, arrayWithFallbackCollectionSlug) + noLocalizedFieldsURL = new AdminUrlUtil(serverURL, noLocalizedFieldsCollectionSlug) context = await browser.newContext() page = await context.newPage() @@ -671,6 +674,13 @@ describe('Localization', () => { await expect(page.locator('#field-title')).toBeEmpty() }) }) + + test('should not show publish specific locale button when no localized fields exist', async () => { + await page.goto(urlPostsWithDrafts.create) + await expect(page.locator('#publish-locale')).toHaveCount(1) + await page.goto(noLocalizedFieldsURL.create) + await expect(page.locator('#publish-locale')).toHaveCount(0) + }) }) async function createLocalizedArrayItem(page: Page, url: AdminUrlUtil) {