fix(posts): allow deleting Failed posts + misc terminal-state polish
- Add separate canDelete predicate in Index.vue that includes Failed — the previous EDITABLE_STATUSES gating hid the delete button for failed posts even though the backend allows deleting them. - Edit.vue Echo handler navigates to /show when an in-page real-time status update transitions the post into a read-only state, instead of leaving the user on a stuck readonly editor. - FacebookPublisher: stop using empty() for content checks (treats literal "0" as empty) — compare explicitly against null and "". - Update terminal-state error messages in API + MCP update tool to reflect the broadened guard (no longer Published-only). Adjust the matching test assertion.
This commit is contained in:
parent
31db72638b
commit
e4f8833608
6 changed files with 20 additions and 15 deletions
|
|
@ -69,7 +69,7 @@ public function update(UpdatePostRequest $request, Post $post): PostResource|Jso
|
|||
|
||||
if (data_get($result, 'action') === PostAction::Finalized) {
|
||||
return response()->json(
|
||||
['message' => 'Cannot edit a published post.'],
|
||||
['message' => 'Cannot edit a post in a terminal state.'],
|
||||
Response::HTTP_UNPROCESSABLE_ENTITY
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ public function handle(Request $request): Response|ResponseFactory
|
|||
$result = UpdatePost::execute($workspace, $post, $payload);
|
||||
|
||||
if (data_get($result, 'action') === PostAction::Finalized) {
|
||||
return Response::error('Cannot edit a published post.');
|
||||
return Response::error('Cannot edit a post in a terminal state.');
|
||||
}
|
||||
|
||||
/** @var Post $updated */
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ private function publishPost(string $pageId, string $accessToken, ?string $conte
|
|||
{
|
||||
// Text only post
|
||||
if ($media->isEmpty()) {
|
||||
if (empty($content)) {
|
||||
if ($content === null || $content === '') {
|
||||
throw new \Exception('Facebook text posts require content. Please add text to your post.');
|
||||
}
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ private function publishSingleImagePost(string $pageId, string $accessToken, ?st
|
|||
'access_token' => $accessToken,
|
||||
];
|
||||
|
||||
if (! empty($content)) {
|
||||
if ($content !== null && $content !== '') {
|
||||
$payload['message'] = $content;
|
||||
}
|
||||
|
||||
|
|
@ -167,7 +167,7 @@ private function publishMultiImagePost(string $pageId, string $accessToken, ?str
|
|||
'access_token' => $accessToken,
|
||||
];
|
||||
|
||||
if (! empty($content)) {
|
||||
if ($content !== null && $content !== '') {
|
||||
$postData['message'] = $content;
|
||||
}
|
||||
|
||||
|
|
@ -201,7 +201,7 @@ private function publishVideoPost(string $pageId, string $accessToken, ?string $
|
|||
'access_token' => $accessToken,
|
||||
];
|
||||
|
||||
if (! empty($content)) {
|
||||
if ($content !== null && $content !== '') {
|
||||
$payload['description'] = $content;
|
||||
}
|
||||
|
||||
|
|
@ -306,7 +306,7 @@ private function publishReel(string $pageId, string $accessToken, ?string $conte
|
|||
'access_token' => $accessToken,
|
||||
];
|
||||
|
||||
if (! empty($content)) {
|
||||
if ($content !== null && $content !== '') {
|
||||
$finishPayload['description'] = $content;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { ContentType } from '@/enums/content-type';
|
|||
import { Platform } from '@/enums/platform';
|
||||
import { PostStatus } from '@/types/post';
|
||||
import AppLayout from '@/layouts/AppLayout.vue';
|
||||
import { destroy as destroyPost, update as updatePost } from '@/routes/app/posts';
|
||||
import { destroy as destroyPost, show as showPost, update as updatePost } from '@/routes/app/posts';
|
||||
import type { PinterestBoard } from '@/types';
|
||||
|
||||
interface MediaItem {
|
||||
|
|
@ -514,12 +514,15 @@ const unschedulePost = () => {
|
|||
submit(PostStatus.Draft);
|
||||
};
|
||||
|
||||
// Echo: listen for real-time platform status updates.
|
||||
// Event fires when any post_platform completes publishing (success or fail).
|
||||
// Full reload of the post prop so the new status + post_platforms propagate and
|
||||
// the overlay dismisses.
|
||||
usePostEcho(post.value.id, '.post.platform.status.updated', () => {
|
||||
router.reload({ only: ['post'] });
|
||||
router.reload({
|
||||
only: ['post'],
|
||||
onSuccess: () => {
|
||||
if (READONLY_STATUSES.includes(post.value.status)) {
|
||||
router.visit(showPost.url(post.value.id));
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -138,7 +138,9 @@ const getPostPreview = (post: Post): string =>
|
|||
post.content?.trim() || trans('calendar.no_content');
|
||||
|
||||
const EDITABLE_STATUSES: readonly string[] = [PostStatus.Draft, PostStatus.Scheduled];
|
||||
const DELETABLE_STATUSES: readonly string[] = [PostStatus.Draft, PostStatus.Scheduled, PostStatus.Failed];
|
||||
const canEdit = (post: Post): boolean => EDITABLE_STATUSES.includes(post.status);
|
||||
const canDelete = (post: Post): boolean => DELETABLE_STATUSES.includes(post.status);
|
||||
|
||||
const postUrl = (post: Post): string =>
|
||||
canEdit(post) ? editPost.url(post.id) : showPost.url(post.id);
|
||||
|
|
@ -303,7 +305,7 @@ useWorkspaceEcho(
|
|||
<IconCopy class="size-4" />
|
||||
{{ $t('posts.actions.copy_id') }}
|
||||
</DropdownMenuItem>
|
||||
<template v-if="canEdit(post)">
|
||||
<template v-if="canDelete(post)">
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@
|
|||
$response = TryPostServer::actingAs($this->user)
|
||||
->tool(UpdatePostTool::class, ['post_id' => $post->id, 'content' => 'x']);
|
||||
|
||||
$response->assertHasErrors(['Cannot edit a published post.']);
|
||||
$response->assertHasErrors(['Cannot edit a post in a terminal state.']);
|
||||
})->with([
|
||||
PostStatus::Published,
|
||||
PostStatus::PartiallyPublished,
|
||||
|
|
|
|||
Loading…
Reference in a new issue