Skip to content

Commit 8afc828

Browse files
tak-ambosskendelljoseph
authored andcommitted
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)
1 parent c2ea68e commit 8afc828

File tree

6 files changed

+96
-1
lines changed

6 files changed

+96
-1
lines changed

packages/ui/src/elements/Status/index.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,19 @@ export const Status: React.FC = () => {
117117
setUnpublishedVersionCount(0)
118118
}
119119
} else {
120-
toast.error(t('error:unPublishingDocument'))
120+
try {
121+
const json = await res.json()
122+
if (json.errors?.[0]?.message) {
123+
toast.error(json.errors[0].message)
124+
} else if (json.error) {
125+
toast.error(json.error)
126+
} else {
127+
toast.error(t('error:unPublishingDocument'))
128+
}
129+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
130+
} catch (err) {
131+
toast.error(t('error:unPublishingDocument'))
132+
}
121133
}
122134
},
123135
[
@@ -154,6 +166,7 @@ export const Status: React.FC = () => {
154166
<Button
155167
buttonStyle="none"
156168
className={`${baseClass}__action`}
169+
id={`action-unpublish`}
157170
onClick={() => toggleModal(unPublishModalSlug)}
158171
>
159172
{t('version:unpublish')}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { CollectionConfig } from 'payload'
2+
3+
import { APIError } from 'payload'
4+
5+
import { errorOnUnpublishSlug } from '../slugs.js'
6+
7+
const ErrorOnUnpublish: CollectionConfig = {
8+
slug: errorOnUnpublishSlug,
9+
admin: {
10+
useAsTitle: 'title',
11+
},
12+
fields: [
13+
{
14+
name: 'title',
15+
type: 'text',
16+
required: true,
17+
},
18+
],
19+
versions: {
20+
drafts: true,
21+
},
22+
hooks: {
23+
beforeValidate: [
24+
({ data, originalDoc }) => {
25+
if (data?._status === 'draft' && originalDoc?._status === 'published') {
26+
throw new APIError('Custom error on unpublish', 400, {}, true)
27+
}
28+
},
29+
],
30+
},
31+
}
32+
33+
export default ErrorOnUnpublish

test/versions/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import DisablePublish from './collections/DisablePublish.js'
1212
import DraftPosts from './collections/Drafts.js'
1313
import DraftWithMax from './collections/DraftsWithMax.js'
1414
import DraftsWithValidate from './collections/DraftsWithValidate.js'
15+
import ErrorOnUnpublish from './collections/ErrorOnUnpublish.js'
1516
import LocalizedPosts from './collections/Localized.js'
1617
import { Media } from './collections/Media.js'
1718
import Posts from './collections/Posts.js'
@@ -42,6 +43,7 @@ export default buildConfigWithDefaults({
4243
DraftPosts,
4344
DraftWithMax,
4445
DraftsWithValidate,
46+
ErrorOnUnpublish,
4547
LocalizedPosts,
4648
VersionPosts,
4749
CustomIDs,

test/versions/e2e.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import {
6060
draftWithMaxCollectionSlug,
6161
draftWithMaxGlobalSlug,
6262
draftWithValidateCollectionSlug,
63+
errorOnUnpublishSlug,
6364
localizedCollectionSlug,
6465
localizedGlobalSlug,
6566
postCollectionSlug,
@@ -86,6 +87,7 @@ describe('Versions', () => {
8687
let disablePublishURL: AdminUrlUtil
8788
let customIDURL: AdminUrlUtil
8889
let postURL: AdminUrlUtil
90+
let errorOnUnpublishURL: AdminUrlUtil
8991
let id: string
9092

9193
beforeAll(async ({ browser }, testInfo) => {
@@ -124,6 +126,7 @@ describe('Versions', () => {
124126
disablePublishURL = new AdminUrlUtil(serverURL, disablePublishSlug)
125127
customIDURL = new AdminUrlUtil(serverURL, customIDSlug)
126128
postURL = new AdminUrlUtil(serverURL, postCollectionSlug)
129+
errorOnUnpublishURL = new AdminUrlUtil(serverURL, errorOnUnpublishSlug)
127130
})
128131

129132
test('collection — has versions tab', async () => {
@@ -579,6 +582,22 @@ describe('Versions', () => {
579582
await expect(page.locator('#action-save')).not.toBeAttached()
580583
})
581584

585+
test('collections — should show custom error message when unpublishing fails', async () => {
586+
const publishedDoc = await payload.create({
587+
collection: errorOnUnpublishSlug,
588+
data: {
589+
_status: 'published',
590+
title: 'title',
591+
},
592+
})
593+
await page.goto(errorOnUnpublishURL.edit(String(publishedDoc.id)))
594+
await page.locator('#action-unpublish').click()
595+
await page.locator('[id^="confirm-un-publish-"] #confirm-action').click()
596+
await expect(
597+
page.locator('.payload-toast-item:has-text("Custom error on unpublish")'),
598+
).toBeVisible()
599+
})
600+
582601
test('should show documents title in relationship even if draft document', async () => {
583602
await payload.create({
584603
collection: autosaveCollectionSlug,

test/versions/payload-types.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export interface Config {
7575
'draft-posts': DraftPost;
7676
'draft-with-max-posts': DraftWithMaxPost;
7777
'draft-with-validate-posts': DraftWithValidatePost;
78+
'error-on-unpublish': ErrorOnUnpublish;
7879
'localized-posts': LocalizedPost;
7980
'version-posts': VersionPost;
8081
'custom-ids': CustomId;
@@ -97,6 +98,7 @@ export interface Config {
9798
'draft-posts': DraftPostsSelect<false> | DraftPostsSelect<true>;
9899
'draft-with-max-posts': DraftWithMaxPostsSelect<false> | DraftWithMaxPostsSelect<true>;
99100
'draft-with-validate-posts': DraftWithValidatePostsSelect<false> | DraftWithValidatePostsSelect<true>;
101+
'error-on-unpublish': ErrorOnUnpublishSelect<false> | ErrorOnUnpublishSelect<true>;
100102
'localized-posts': LocalizedPostsSelect<false> | LocalizedPostsSelect<true>;
101103
'version-posts': VersionPostsSelect<false> | VersionPostsSelect<true>;
102104
'custom-ids': CustomIdsSelect<false> | CustomIdsSelect<true>;
@@ -289,6 +291,17 @@ export interface DraftWithValidatePost {
289291
createdAt: string;
290292
_status?: ('draft' | 'published') | null;
291293
}
294+
/**
295+
* This interface was referenced by `Config`'s JSON-Schema
296+
* via the `definition` "error-on-unpublish".
297+
*/
298+
export interface ErrorOnUnpublish {
299+
id: string;
300+
title: string;
301+
updatedAt: string;
302+
createdAt: string;
303+
_status?: ('draft' | 'published') | null;
304+
}
292305
/**
293306
* This interface was referenced by `Config`'s JSON-Schema
294307
* via the `definition` "localized-posts".
@@ -589,6 +602,10 @@ export interface PayloadLockedDocument {
589602
relationTo: 'draft-with-validate-posts';
590603
value: string | DraftWithValidatePost;
591604
} | null)
605+
| ({
606+
relationTo: 'error-on-unpublish';
607+
value: string | ErrorOnUnpublish;
608+
} | null)
592609
| ({
593610
relationTo: 'localized-posts';
594611
value: string | LocalizedPost;
@@ -778,6 +795,16 @@ export interface DraftWithValidatePostsSelect<T extends boolean = true> {
778795
createdAt?: T;
779796
_status?: T;
780797
}
798+
/**
799+
* This interface was referenced by `Config`'s JSON-Schema
800+
* via the `definition` "error-on-unpublish_select".
801+
*/
802+
export interface ErrorOnUnpublishSelect<T extends boolean = true> {
803+
title?: T;
804+
updatedAt?: T;
805+
createdAt?: T;
806+
_status?: T;
807+
}
781808
/**
782809
* This interface was referenced by `Config`'s JSON-Schema
783810
* via the `definition` "localized-posts_select".

test/versions/slugs.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const mediaCollectionSlug = 'media'
1919
export const versionCollectionSlug = 'version-posts'
2020

2121
export const disablePublishSlug = 'disable-publish'
22+
export const errorOnUnpublishSlug = 'error-on-unpublish'
2223

2324
export const disablePublishGlobalSlug = 'disable-publish-global'
2425

0 commit comments

Comments
 (0)