diff --git a/src/components/comment-content/__tests__/comment-content.test.tsx b/src/components/comment-content/__tests__/comment-content.test.tsx
index 67723a62..be631c63 100644
--- a/src/components/comment-content/__tests__/comment-content.test.tsx
+++ b/src/components/comment-content/__tests__/comment-content.test.tsx
@@ -397,7 +397,8 @@ describe('CommentContent', () => {
postCid: 'post-2',
state: 'failed',
});
- expect(container.textContent).toContain('Failed to publish');
+ expect(container.textContent).not.toContain('Failed to publish');
+ expect(container.textContent).toContain('still pending');
expect(container.querySelector('[data-testid="loading-ellipsis"]')).toBeNull();
testState.stateString = 'Publishing';
diff --git a/src/components/comment-content/comment-content.tsx b/src/components/comment-content/comment-content.tsx
index ee7e9cf4..a34dbe57 100644
--- a/src/components/comment-content/comment-content.tsx
+++ b/src/components/comment-content/comment-content.tsx
@@ -131,6 +131,7 @@ const CommentContent = ({ comment: post, prependContent }: { comment: Comment; p
const stateString = useStateString(resolvedPost);
const hasFailedState = state === 'failed';
const failedError = getFailedCommentError(resolvedPost);
+ const shouldShowUnpublishedStateDetails = !cid && (!hasFailedState || Boolean(failedError));
const loadingString = (
@@ -298,7 +299,7 @@ const CommentContent = ({ comment: post, prependContent }: { comment: Comment; p
)}
- {!cid && (
+ {shouldShowUnpublishedStateDetails && (
<>
diff --git a/src/components/post-desktop/post-desktop.tsx b/src/components/post-desktop/post-desktop.tsx
index 7e381981..02ab961b 100644
--- a/src/components/post-desktop/post-desktop.tsx
+++ b/src/components/post-desktop/post-desktop.tsx
@@ -384,7 +384,7 @@ const PostInfo = ({
) : purged ? (
t('purged')
) : !cid && pseudonymityMode ? (
- {hasFailedState ? capitalize(t('failed')) : capitalize(t('pending'))}
+ {hasFailedState ? '?' : capitalize(t('pending'))}
) : (
No.
- {hasFailedState ? capitalize(t('failed')) : capitalize(t('pending'))}
+ {hasFailedState ? '?' : capitalize(t('pending'))}
>
)}
{pinned && (
@@ -855,6 +855,7 @@ const PostDesktop = ({
const isMultiboardView = isInAllView || isInSubscriptionsView || isInModView;
const directories = useDirectories();
const boardPath = communityAddress ? getBoardPath(communityAddress, directories) : undefined;
+ const deleteFailedPostRedirectPath = isInPendingPostView ? (boardPath ? `/${boardPath}` : '/') : undefined;
const displayBoardPath =
boardPath && communityAddress
? boardPath !== communityAddress
@@ -957,8 +958,10 @@ const PostDesktop = ({
const stateString = useStateString(resolvedPost) || t('downloading_board');
const hasFailedState = state === 'failed';
- const { canDeleteFailedPost, canRetryFailedPost, isDeletingFailedPost, isRetryingFailedPost, onDeleteFailedPost, onRetryFailedPost } =
- useDeleteFailedPost(resolvedPost);
+ const { canDeleteFailedPost, canRetryFailedPost, isDeletingFailedPost, isRetryingFailedPost, onDeleteFailedPost, onRetryFailedPost } = useDeleteFailedPost(
+ resolvedPost,
+ deleteFailedPostRedirectPath,
+ );
const failedPublishNotice = canDeleteFailedPost ? (
{hasFailedState ? capitalize(t('failed')) : capitalize(t('pending'))}
+ {hasFailedState ? '?' : capitalize(t('pending'))}
) : (
No.
- {hasFailedState ? capitalize(t('failed')) : capitalize(t('pending'))}
+ {hasFailedState ? '?' : capitalize(t('pending'))}
>
)}
{shouldShowPendingApprovalButtons && (
@@ -595,6 +595,7 @@ const PostMobile = ({
) : null;
const linksCount = useCountLinksInReplies(resolvedPost);
+ const deleteFailedPostRedirectPath = isInPendingPostView ? (boardPath ? `/${boardPath}` : '/') : undefined;
const hasReplyPaginationOverride = !!replyPaginationOverride;
const shouldFetchReplies = showReplies && !isModQueue && !hasReplyPaginationOverride;
const shouldUsePreview = shouldFetchReplies && !showAllReplies;
@@ -668,8 +669,10 @@ const PostMobile = ({
const stateString = useStateString(resolvedPost) || t('loading_post');
const hasFailedState = state === 'failed';
const isReply = !!parentCid;
- const { canDeleteFailedPost, canRetryFailedPost, isDeletingFailedPost, isRetryingFailedPost, onDeleteFailedPost, onRetryFailedPost } =
- useDeleteFailedPost(resolvedPost);
+ const { canDeleteFailedPost, canRetryFailedPost, isDeletingFailedPost, isRetryingFailedPost, onDeleteFailedPost, onRetryFailedPost } = useDeleteFailedPost(
+ resolvedPost,
+ deleteFailedPostRedirectPath,
+ );
const failedPublishNotice = canDeleteFailedPost ? (
({
alertMock: vi.fn(),
deleteCommentMock: vi.fn(async (_targetComment: string | number) => undefined),
lastPublishOptions: undefined as Record | undefined,
+ navigateMock: vi.fn(),
publishCommentMock: vi.fn(async () => undefined),
}));
+vi.mock('react-router-dom', () => ({
+ useNavigate: () => testState.navigateMock,
+}));
+
vi.mock('@bitsocial/bitsocial-react-hooks', () => ({
deleteComment: (targetComment: string | number) => testState.deleteCommentMock(targetComment),
usePublishComment: (options: Record | undefined) => {
@@ -63,14 +68,14 @@ const failedPost = {
title: 'Hello',
};
-const HookHarness = ({ post }: { post: typeof failedPost }) => {
- latestValue = useDeleteFailedPost(post);
+const HookHarness = ({ deleteRedirectPath, post }: { deleteRedirectPath?: string; post: typeof failedPost }) => {
+ latestValue = useDeleteFailedPost(post, deleteRedirectPath);
return null;
};
-const renderHook = (post = failedPost) => {
+const renderHook = (post = failedPost, deleteRedirectPath?: string) => {
act(() => {
- root.render(createElement(HookHarness, { post }));
+ root.render(createElement(HookHarness, { deleteRedirectPath, post }));
});
};
@@ -137,6 +142,17 @@ describe('useDeleteFailedPost', () => {
expect(testState.publishCommentMock).toHaveBeenCalledTimes(1);
});
+ it('redirects after deleting a failed post when a redirect path is provided', async () => {
+ renderHook(failedPost, '/mu');
+
+ await act(async () => {
+ await latestValue.onDeleteFailedPost();
+ });
+
+ expect(testState.deleteCommentMock).toHaveBeenCalledWith('failed-cid');
+ expect(testState.navigateMock).toHaveBeenCalledWith('/mu', { replace: true });
+ });
+
it('routes retry challenges through the challenge store and preserves abandon behavior', async () => {
await act(async () => {
await testState.lastPublishOptions?.onChallenge('captcha', 'nonce');
diff --git a/src/hooks/use-delete-failed-post.ts b/src/hooks/use-delete-failed-post.ts
index 6ba62ff2..80b73d86 100644
--- a/src/hooks/use-delete-failed-post.ts
+++ b/src/hooks/use-delete-failed-post.ts
@@ -1,4 +1,5 @@
import { useCallback, useMemo, useRef, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
import { ChallengeVerification, Comment, PublishCommentOptions, deleteComment, usePublishComment } from '@bitsocial/bitsocial-react-hooks';
import { alertChallengeVerificationFailed } from '../lib/utils/challenge-utils';
import useChallengesStore from '../stores/use-challenges-store';
@@ -67,10 +68,11 @@ export const getFailedPostRetryPublishOptions = (post?: FailedPost): PublishComm
return retryOptions;
};
-const useDeleteFailedPost = (post?: FailedPost) => {
+const useDeleteFailedPost = (post?: FailedPost, deleteRedirectPath?: string) => {
const [isDeletingFailedPost, setIsDeletingFailedPost] = useState(false);
const [isRetryingFailedPost, setIsRetryingFailedPost] = useState(false);
const addChallenge = useChallengesStore((state) => state.addChallenge);
+ const navigate = useNavigate();
const abandonPublishRef = useRef<(() => Promise) | undefined>();
const abandonCurrentPublish = useCallback(async () => {
await abandonPublishRef.current?.();
@@ -114,13 +116,16 @@ const useDeleteFailedPost = (post?: FailedPost) => {
deleteComment(targetComment)
.then(() => {
setIsDeletingFailedPost(false);
+ if (deleteRedirectPath) {
+ navigate(deleteRedirectPath, { replace: true });
+ }
})
.catch((error) => {
console.error('Failed to delete failed post:', error);
alert(`Failed to delete post: ${error instanceof Error ? error.message : 'Unknown error'}`);
setIsDeletingFailedPost(false);
});
- }, [canDeleteFailedPost, isDeletingFailedPost, isRetryingFailedPost, post]);
+ }, [canDeleteFailedPost, deleteRedirectPath, isDeletingFailedPost, isRetryingFailedPost, navigate, post]);
const canRetryFailedPost = canDeleteFailedPost && Boolean(retryPublishOptions);