feat(ui): display the actual error message on unpublish if available (#11898)

### What?
If an error occurs while unpublishing a document in the edit view UI,
the toast which shows the error message now displays the actual message
which is sent from the server, if available.

### Why?
Only a generic error message was shown if an unpublish operation failed.
Some errors might be solvable by the user, so that there is value in
showing the actual, actionable error message instead of a generic one.

### How?
The server response is parsed for error message if an unpublish
operation fails and displayed in the toast, instead of the generic error
message.


![image](https://github.com/user-attachments/assets/774d68c6-b36b-4447-93a0-b437845694a9)
This commit is contained in:
Tobias Odendahl
2025-05-07 02:27:05 +02:00
committed by GitHub
parent 05ae957cd5
commit b3cac753d6
6 changed files with 96 additions and 1 deletions

View File

@@ -0,0 +1,33 @@
import type { CollectionConfig } from 'payload'
import { APIError } from 'payload'
import { errorOnUnpublishSlug } from '../slugs.js'
const ErrorOnUnpublish: CollectionConfig = {
slug: errorOnUnpublishSlug,
admin: {
useAsTitle: 'title',
},
fields: [
{
name: 'title',
type: 'text',
required: true,
},
],
versions: {
drafts: true,
},
hooks: {
beforeValidate: [
({ data, originalDoc }) => {
if (data?._status === 'draft' && originalDoc?._status === 'published') {
throw new APIError('Custom error on unpublish', 400, {}, true)
}
},
],
},
}
export default ErrorOnUnpublish

View File

@@ -12,6 +12,7 @@ import DisablePublish from './collections/DisablePublish.js'
import DraftPosts from './collections/Drafts.js'
import DraftWithMax from './collections/DraftsWithMax.js'
import DraftsWithValidate from './collections/DraftsWithValidate.js'
import ErrorOnUnpublish from './collections/ErrorOnUnpublish.js'
import LocalizedPosts from './collections/Localized.js'
import { Media } from './collections/Media.js'
import Posts from './collections/Posts.js'
@@ -42,6 +43,7 @@ export default buildConfigWithDefaults({
DraftPosts,
DraftWithMax,
DraftsWithValidate,
ErrorOnUnpublish,
LocalizedPosts,
VersionPosts,
CustomIDs,

View File

@@ -60,6 +60,7 @@ import {
draftWithMaxCollectionSlug,
draftWithMaxGlobalSlug,
draftWithValidateCollectionSlug,
errorOnUnpublishSlug,
localizedCollectionSlug,
localizedGlobalSlug,
postCollectionSlug,
@@ -86,6 +87,7 @@ describe('Versions', () => {
let disablePublishURL: AdminUrlUtil
let customIDURL: AdminUrlUtil
let postURL: AdminUrlUtil
let errorOnUnpublishURL: AdminUrlUtil
let id: string
beforeAll(async ({ browser }, testInfo) => {
@@ -124,6 +126,7 @@ describe('Versions', () => {
disablePublishURL = new AdminUrlUtil(serverURL, disablePublishSlug)
customIDURL = new AdminUrlUtil(serverURL, customIDSlug)
postURL = new AdminUrlUtil(serverURL, postCollectionSlug)
errorOnUnpublishURL = new AdminUrlUtil(serverURL, errorOnUnpublishSlug)
})
test('collection — has versions tab', async () => {
@@ -579,6 +582,22 @@ describe('Versions', () => {
await expect(page.locator('#action-save')).not.toBeAttached()
})
test('collections — should show custom error message when unpublishing fails', async () => {
const publishedDoc = await payload.create({
collection: errorOnUnpublishSlug,
data: {
_status: 'published',
title: 'title',
},
})
await page.goto(errorOnUnpublishURL.edit(String(publishedDoc.id)))
await page.locator('#action-unpublish').click()
await page.locator('[id^="confirm-un-publish-"] #confirm-action').click()
await expect(
page.locator('.payload-toast-item:has-text("Custom error on unpublish")'),
).toBeVisible()
})
test('should show documents title in relationship even if draft document', async () => {
await payload.create({
collection: autosaveCollectionSlug,

View File

@@ -75,6 +75,7 @@ export interface Config {
'draft-posts': DraftPost;
'draft-with-max-posts': DraftWithMaxPost;
'draft-with-validate-posts': DraftWithValidatePost;
'error-on-unpublish': ErrorOnUnpublish;
'localized-posts': LocalizedPost;
'version-posts': VersionPost;
'custom-ids': CustomId;
@@ -97,6 +98,7 @@ export interface Config {
'draft-posts': DraftPostsSelect<false> | DraftPostsSelect<true>;
'draft-with-max-posts': DraftWithMaxPostsSelect<false> | DraftWithMaxPostsSelect<true>;
'draft-with-validate-posts': DraftWithValidatePostsSelect<false> | DraftWithValidatePostsSelect<true>;
'error-on-unpublish': ErrorOnUnpublishSelect<false> | ErrorOnUnpublishSelect<true>;
'localized-posts': LocalizedPostsSelect<false> | LocalizedPostsSelect<true>;
'version-posts': VersionPostsSelect<false> | VersionPostsSelect<true>;
'custom-ids': CustomIdsSelect<false> | CustomIdsSelect<true>;
@@ -289,6 +291,17 @@ export interface DraftWithValidatePost {
createdAt: string;
_status?: ('draft' | 'published') | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "error-on-unpublish".
*/
export interface ErrorOnUnpublish {
id: string;
title: string;
updatedAt: string;
createdAt: string;
_status?: ('draft' | 'published') | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "localized-posts".
@@ -589,6 +602,10 @@ export interface PayloadLockedDocument {
relationTo: 'draft-with-validate-posts';
value: string | DraftWithValidatePost;
} | null)
| ({
relationTo: 'error-on-unpublish';
value: string | ErrorOnUnpublish;
} | null)
| ({
relationTo: 'localized-posts';
value: string | LocalizedPost;
@@ -778,6 +795,16 @@ export interface DraftWithValidatePostsSelect<T extends boolean = true> {
createdAt?: T;
_status?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "error-on-unpublish_select".
*/
export interface ErrorOnUnpublishSelect<T extends boolean = true> {
title?: T;
updatedAt?: T;
createdAt?: T;
_status?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "localized-posts_select".

View File

@@ -19,6 +19,7 @@ export const mediaCollectionSlug = 'media'
export const versionCollectionSlug = 'version-posts'
export const disablePublishSlug = 'disable-publish'
export const errorOnUnpublishSlug = 'error-on-unpublish'
export const disablePublishGlobalSlug = 'disable-publish-global'