diff --git a/__tests__/e2e/admin/collections/tenants.e2e.spec.ts b/__tests__/e2e/admin/collections/tenants.e2e.spec.ts index 0a404d8e..0d83b33b 100644 --- a/__tests__/e2e/admin/collections/tenants.e2e.spec.ts +++ b/__tests__/e2e/admin/collections/tenants.e2e.spec.ts @@ -4,6 +4,7 @@ import { CollectionSlugs, getSelectInputOptions, getSelectInputValue, + isSelectReadOnly, openDocControls, selectInput, waitForFormReady, @@ -58,22 +59,28 @@ authTest.describe('Tenants', () => { authTest.describe('Read', () => { authTest.describe.configure({ timeout: 60000 }) - authTest('slug field shows current value on existing tenant', async ({ adminPage }) => { - await adminPage.goto(tenantsUrl.list) - await adminPage.waitForLoadState('networkidle') + authTest( + 'slug field shows current value and read-only on existing tenant', + async ({ adminPage }) => { + await adminPage.goto(tenantsUrl.list) + await adminPage.waitForLoadState('networkidle') - const firstLink = adminPage.locator('table tbody tr td a').first() - await firstLink.waitFor({ timeout: 15000 }) - await firstLink.click() - await adminPage.waitForURL(/\/collections\/tenants\/\d+/) - await waitForFormReady(adminPage) + const firstLink = adminPage.locator('table tbody tr td a').first() + await firstLink.waitFor({ timeout: 15000 }) + await firstLink.click() + await adminPage.waitForURL(/\/collections\/tenants\/\d+/) + await waitForFormReady(adminPage) - const slugField = adminPage.locator('#field-slug') - await expect(slugField).toBeVisible() + const slugField = adminPage.locator('#field-slug') + await expect(slugField).toBeVisible() - const value = await getSelectInputValue({ selectLocator: slugField }) - expect(value).toBeTruthy() - }) + const value = await getSelectInputValue({ selectLocator: slugField }) + expect(value).toBeTruthy() + + const readOnly = await isSelectReadOnly({ selectLocator: slugField }) + expect(readOnly).toBe(true) + }, + ) }) authTest.describe('Delete', () => { diff --git a/src/app/(payload)/admin/importMap.js b/src/app/(payload)/admin/importMap.js index e9f8d33a..b4f52df4 100644 --- a/src/app/(payload)/admin/importMap.js +++ b/src/app/(payload)/admin/importMap.js @@ -35,6 +35,7 @@ import { InviteUser as InviteUser_6042b6804e11048cd4fbe6206cbc2b0f } from '@/col import { ResendInviteButton as ResendInviteButton_e262b7912e5bdc08a1a83eb2731de735 } from '@/collections/Users/components/ResendInviteButton' import { CollectionsField as CollectionsField_49c0311020325b59204cc21d2f536b8d } from '@/collections/Roles/components/CollectionsField' import { RulesCell as RulesCell_649699f5b285e7a5429592dc58fd6f0c } from '@/collections/Roles/components/RulesCell' +import { SlugCell as SlugCell_a08dd702af9ee7f45ad5b0b5fc74319d } from '@/collections/Tenants/components/SlugCell' import { TenantSlugField as TenantSlugField_1aeaed4308ae318944aa6215b1567366 } from '@/collections/Tenants/components/TenantSlugField' import { OnboardingStatusCell as OnboardingStatusCell_132cf100d66efa575804a025c9c1c699 } from '@/collections/Tenants/components/OnboardingStatusCell' import { OnboardingChecklist as OnboardingChecklist_e43d4b78209dd849b6f9ccc557d93063 } from '@/collections/Tenants/components/OnboardingChecklist' @@ -99,6 +100,7 @@ export const importMap = { "@/collections/Users/components/ResendInviteButton#ResendInviteButton": ResendInviteButton_e262b7912e5bdc08a1a83eb2731de735, "@/collections/Roles/components/CollectionsField#CollectionsField": CollectionsField_49c0311020325b59204cc21d2f536b8d, "@/collections/Roles/components/RulesCell#RulesCell": RulesCell_649699f5b285e7a5429592dc58fd6f0c, + "@/collections/Tenants/components/SlugCell#SlugCell": SlugCell_a08dd702af9ee7f45ad5b0b5fc74319d, "@/collections/Tenants/components/TenantSlugField#TenantSlugField": TenantSlugField_1aeaed4308ae318944aa6215b1567366, "@/collections/Tenants/components/OnboardingStatusCell#OnboardingStatusCell": OnboardingStatusCell_132cf100d66efa575804a025c9c1c699, "@/collections/Tenants/components/OnboardingChecklist#OnboardingChecklist": OnboardingChecklist_e43d4b78209dd849b6f9ccc557d93063, diff --git a/src/collections/Tenants/components/SlugCell.tsx b/src/collections/Tenants/components/SlugCell.tsx new file mode 100644 index 00000000..42c2293e --- /dev/null +++ b/src/collections/Tenants/components/SlugCell.tsx @@ -0,0 +1,6 @@ +import type { DefaultServerCellComponentProps } from 'payload' + +export function SlugCell({ cellData }: Pick) { + if (typeof cellData !== 'string') return null + return <>{cellData.toUpperCase()} +} diff --git a/src/collections/Tenants/components/TenantSlugField.tsx b/src/collections/Tenants/components/TenantSlugField.tsx index c9cf4cda..6a6840e0 100644 --- a/src/collections/Tenants/components/TenantSlugField.tsx +++ b/src/collections/Tenants/components/TenantSlugField.tsx @@ -1,12 +1,30 @@ import type { SelectFieldServerComponent } from 'payload' -import { SelectField } from '@payloadcms/ui' +import { SelectField, SelectInput } from '@payloadcms/ui' export const TenantSlugField: SelectFieldServerComponent = async ({ clientField, + data, field, payload, }) => { + const currentSlug = data?.slug + // Slug is immutable after creation + if (data.id) { + return ( + + ) + } + const { docs } = await payload.find({ collection: 'tenants', limit: 0, @@ -18,7 +36,8 @@ export const TenantSlugField: SelectFieldServerComponent = async ({ const options = clientField.options?.filter((option) => { const value = typeof option === 'string' ? option : option.value - return !usedSlugs.has(value) + // Keep the current document's slug so the label displays correctly + return value === currentSlug || !usedSlugs.has(value) }) return diff --git a/src/collections/Tenants/index.ts b/src/collections/Tenants/index.ts index cf58c30a..25367e04 100644 --- a/src/collections/Tenants/index.ts +++ b/src/collections/Tenants/index.ts @@ -53,12 +53,13 @@ export const Tenants: CollectionConfig = { type: 'select', admin: { components: { + Cell: '@/collections/Tenants/components/SlugCell#SlugCell', Field: '@/collections/Tenants/components/TenantSlugField#TenantSlugField', }, description: 'Avalanche center identifier. Used for subdomains and URL paths.', }, options: VALID_TENANT_SLUGS.map((slug) => ({ - label: `${AVALANCHE_CENTERS[slug].name} (${slug})`, + label: `${slug.toUpperCase()} — ${AVALANCHE_CENTERS[slug].name}`, value: slug, })), index: true,