diff --git a/apps/socialmon/src/components/posts/PostList/PostDetail.tsx b/apps/socialmon/src/components/posts/PostList/PostDetail.tsx
index 7294dda8b..1ac296412 100644
--- a/apps/socialmon/src/components/posts/PostList/PostDetail.tsx
+++ b/apps/socialmon/src/components/posts/PostList/PostDetail.tsx
@@ -21,8 +21,8 @@ import { trpc } from '~/hooks/trpc';
import type { PostExtended } from '~/types';
-import PostCommentsList from '../comments/PostCommentsList';
import { parseMarkdown } from '../../common/ParseMarkdown';
+import PostCommentsList from '../comments/PostCommentsList';
import PostRelevanceActionButton from '../PostRelevanceActionButton';
import PostReplyStatusActionButton from '../PostReplyStatusActionButton';
import PostMetadata from './PostMetadata';
diff --git a/apps/socialmon/src/components/posts/PostList/PostList.tsx b/apps/socialmon/src/components/posts/PostList/PostList.tsx
index 8e094d12d..26846ebd4 100644
--- a/apps/socialmon/src/components/posts/PostList/PostList.tsx
+++ b/apps/socialmon/src/components/posts/PostList/PostList.tsx
@@ -76,13 +76,6 @@ export default function PostList() {
All
- }
- withArrow={true}>
-
- Pending
-
-
}
withArrow={true}>
@@ -99,6 +92,13 @@ export default function PostList() {
Irrelevant
+ }
+ withArrow={true}>
+
+ Pending
+
+
diff --git a/apps/socialmon/src/components/posts/PostRelevanceActionButton.tsx b/apps/socialmon/src/components/posts/PostRelevanceActionButton.tsx
index f5448f97f..82dd35272 100644
--- a/apps/socialmon/src/components/posts/PostRelevanceActionButton.tsx
+++ b/apps/socialmon/src/components/posts/PostRelevanceActionButton.tsx
@@ -7,6 +7,7 @@ import { trpc } from '~/hooks/trpc';
import useCurrentProjectSlug from '~/hooks/useCurrentProjectSlug';
import ShortcutDisplay from '~/components/common/ShortcutDisplay';
+import { useOptionalPostsContext } from '~/components/posts/PostsContext';
import { ShortcutAction } from '~/config/shortcuts';
import { PostRelevancy } from '~/prisma/client';
@@ -27,30 +28,42 @@ export default function PostRelevanceActionButton({
const markPostRelevancyMutation =
trpc.socialPosts.markPostRelevancy.useMutation();
+ // Try to get context (undefined if not in PostsProvider)
+ const context = useOptionalPostsContext();
+ const { markPostRelevancy: contextMarkPostRelevancy } = context || {};
+
+ const targetRelevancy =
+ relevancy === PostRelevancy.IRRELEVANT
+ ? PostRelevancy.RELEVANT
+ : PostRelevancy.IRRELEVANT;
+
const onMarkPostRelevancy = () => {
- markPostRelevancyMutation.mutate(
- {
- postId,
- projectSlug,
- relevancy:
- relevancy === PostRelevancy.IRRELEVANT
- ? PostRelevancy.RELEVANT
- : PostRelevancy.IRRELEVANT,
- },
- {
- onSuccess() {
- // Fast update for posts list (badge appears immediately)
- utils.socialPosts.getPosts.invalidate();
- // Comprehensive update for everything else (button text updates)
- router.refresh();
- toast.success(
- relevancy === PostRelevancy.IRRELEVANT
- ? 'Marked the post as relevant successfully!'
- : 'Marked the post as irrelevant successfully!',
- );
+ if (contextMarkPostRelevancy) {
+ // Use context method with auto-navigation (when in post list)
+ contextMarkPostRelevancy(postId, targetRelevancy);
+ } else {
+ // Fall back to direct mutation (when in standalone post)
+ markPostRelevancyMutation.mutate(
+ {
+ postId,
+ projectSlug,
+ relevancy: targetRelevancy,
},
- },
- );
+ {
+ onSuccess() {
+ // Fast update for posts list (badge appears immediately)
+ utils.socialPosts.getPosts.invalidate();
+ // Comprehensive update for everything else (button text updates)
+ router.refresh();
+ toast.success(
+ relevancy === PostRelevancy.IRRELEVANT
+ ? 'Marked the post as relevant successfully!'
+ : 'Marked the post as irrelevant successfully!',
+ );
+ },
+ },
+ );
+ }
};
const label =
diff --git a/apps/socialmon/src/components/posts/PostReplyStatusActionButton.tsx b/apps/socialmon/src/components/posts/PostReplyStatusActionButton.tsx
index 851633136..dae3916e9 100644
--- a/apps/socialmon/src/components/posts/PostReplyStatusActionButton.tsx
+++ b/apps/socialmon/src/components/posts/PostReplyStatusActionButton.tsx
@@ -7,6 +7,7 @@ import { trpc } from '~/hooks/trpc';
import useCurrentProjectSlug from '~/hooks/useCurrentProjectSlug';
import ShortcutDisplay from '~/components/common/ShortcutDisplay';
+import { useOptionalPostsContext } from '~/components/posts/PostsContext';
import { ShortcutAction } from '~/config/shortcuts';
import { PostRepliedStatus } from '~/prisma/client';
@@ -28,32 +29,48 @@ export default function PostReplyStatusActionButton({
const markPostReplyStatusMutation =
trpc.socialPosts.markPostReplyStatus.useMutation();
- const onMarkPostReplyStatus = () => {
- const newStatus =
- replyStatus === PostRepliedStatus.NOT_REPLIED
- ? PostRepliedStatus.REPLIED_MANUALLY
- : PostRepliedStatus.NOT_REPLIED;
+ // Try to get context (undefined if not in PostsProvider)
+ const context = useOptionalPostsContext();
+ const { markPostReplyStatus: contextMarkPostReplyStatus } = context || {};
- markPostReplyStatusMutation.mutate(
- {
- postId,
- projectSlug,
- replyStatus: newStatus,
- },
- {
- onSuccess() {
- // Fast update for posts list (badge appears immediately)
- utils.socialPosts.getPosts.invalidate();
- // Comprehensive update for everything else (button text updates)
- router.refresh();
- toast.success(
- newStatus === PostRepliedStatus.REPLIED_MANUALLY
- ? 'Marked the post as replied successfully!'
- : 'Marked the post as not replied successfully!',
- );
+ const newStatus =
+ replyStatus === PostRepliedStatus.NOT_REPLIED
+ ? PostRepliedStatus.REPLIED_MANUALLY
+ : PostRepliedStatus.NOT_REPLIED;
+
+ // Convert to context type
+ const contextReplyStatus: 'NOT_REPLIED' | 'REPLIED_MANUALLY' =
+ newStatus === PostRepliedStatus.REPLIED_MANUALLY
+ ? 'REPLIED_MANUALLY'
+ : 'NOT_REPLIED';
+
+ const onMarkPostReplyStatus = () => {
+ if (contextMarkPostReplyStatus) {
+ // Use context method with auto-navigation (when in post list)
+ contextMarkPostReplyStatus(postId, contextReplyStatus);
+ } else {
+ // Fall back to direct mutation (when in standalone post)
+ markPostReplyStatusMutation.mutate(
+ {
+ postId,
+ projectSlug,
+ replyStatus: newStatus,
},
- },
- );
+ {
+ onSuccess() {
+ // Fast update for posts list (badge appears immediately)
+ utils.socialPosts.getPosts.invalidate();
+ // Comprehensive update for everything else (button text updates)
+ router.refresh();
+ toast.success(
+ newStatus === PostRepliedStatus.REPLIED_MANUALLY
+ ? 'Marked the post as replied successfully!'
+ : 'Marked the post as not replied successfully!',
+ );
+ },
+ },
+ );
+ }
};
// Don't show button if post was replied via app
diff --git a/apps/socialmon/src/components/posts/PostsContext.tsx b/apps/socialmon/src/components/posts/PostsContext.tsx
index 58290149b..162d65399 100644
--- a/apps/socialmon/src/components/posts/PostsContext.tsx
+++ b/apps/socialmon/src/components/posts/PostsContext.tsx
@@ -1,7 +1,14 @@
'use client';
import { useParams, useRouter } from 'next/navigation';
-import { createContext, useContext, useEffect, useMemo, useState } from 'react';
+import {
+ createContext,
+ useContext,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from 'react';
import { trpc } from '~/hooks/trpc';
@@ -52,6 +59,89 @@ export function PostsProvider({
const params = useParams();
const utils = trpc.useUtils();
+ // Store navigation context before mutations to handle auto-navigation
+ const navigationContextRef = useRef<{
+ currentIndex: number;
+ postsSnapshot: Array;
+ } | null>(null);
+
+ // Helper function to find the next logical post after a state change
+ const findNextLogicalPost = (
+ currentPostId: string,
+ postsSnapshot: Array,
+ ): string | null => {
+ const currentIndex = postsSnapshot.findIndex(
+ (post) => post.id === currentPostId,
+ );
+
+ if (currentIndex === -1) return null;
+
+ // Try next post first
+ if (currentIndex < postsSnapshot.length - 1) {
+ return postsSnapshot[currentIndex + 1]!.id;
+ }
+
+ // If no next post, try previous post
+ if (currentIndex > 0) {
+ return postsSnapshot[currentIndex - 1]!.id;
+ }
+
+ // No adjacent posts available
+ return null;
+ };
+
+ // Helper function to determine if auto-navigation should happen based on current tab
+ const shouldAutoNavigateForTab = (
+ currentTab: PostListTab,
+ actionType: 'relevancy' | 'reply',
+ ): boolean => {
+ switch (currentTab) {
+ case 'ALL':
+ // Posts always stay visible in ALL tab, no navigation needed
+ return false;
+
+ case 'PENDING':
+ // Navigate when marking as replied or irrelevant (removes from pending)
+ return actionType === 'reply' || actionType === 'relevancy';
+
+ case 'REPLIED':
+ // Navigate when marking as not replied (removes from replied tab)
+ return actionType === 'reply';
+
+ case 'IRRELEVANT':
+ // Navigate when marking as relevant (removes from irrelevant tab)
+ return actionType === 'relevancy';
+
+ default:
+ // Conservative default: navigate for unknown tabs
+ return true;
+ }
+ };
+
+ // Helper function to handle auto-navigation after successful mutation
+ const handleAutoNavigation = (
+ postId: string,
+ actionType: 'relevancy' | 'reply',
+ ): void => {
+ // Only navigate if current post was selected and we have navigation context
+ if (selectedPostId === postId && navigationContextRef.current) {
+ const shouldNavigate = shouldAutoNavigateForTab(activeTab, actionType);
+
+ if (shouldNavigate) {
+ const nextPostId = findNextLogicalPost(
+ postId,
+ navigationContextRef.current.postsSnapshot,
+ );
+
+ if (nextPostId) {
+ // Navigate to next post immediately
+ setSelectedPostId(nextPostId);
+ router.push(`/projects/${projectSlug}/posts/${nextPostId}`);
+ }
+ }
+ }
+ };
+
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
trpc.socialPosts.getPosts.useInfiniteQuery(
{
@@ -106,12 +196,18 @@ export function PostsProvider({
function handlePrevPost() {
if (adjacentPosts.prev) {
handlePostClick(adjacentPosts.prev.id);
+ } else if (posts.length > 0) {
+ // Fallback: if no prev post but we have posts, go to the first post
+ handlePostClick(posts[0]!.id);
}
}
function handleNextPost() {
if (adjacentPosts.next) {
handlePostClick(adjacentPosts.next.id);
+ } else if (posts.length > 0) {
+ // Fallback: if no next post but we have posts, go to the last post
+ handlePostClick(posts[posts.length - 1]!.id);
}
}
@@ -123,6 +219,14 @@ export function PostsProvider({
trpc.socialPosts.markPostReplyStatus.useMutation();
function markPostRelevancy(postId: string, relevancy: PostRelevancy) {
+ // Capture navigation context before mutation
+ const currentIndex = posts.findIndex((post) => post.id === postId);
+
+ navigationContextRef.current = {
+ currentIndex,
+ postsSnapshot: [...posts], // Create a snapshot
+ };
+
markPostRelevancyMutation.mutate(
{
postId,
@@ -130,15 +234,35 @@ export function PostsProvider({
relevancy,
},
{
+ onError() {
+ // Clear navigation context on error
+ navigationContextRef.current = null;
+ },
onSuccess() {
+ // First, invalidate and refresh the data
utils.socialPosts.getPosts.invalidate();
+
+ // Handle auto-navigation if needed
+ handleAutoNavigation(postId, 'relevancy');
+
router.refresh();
+
+ // Clear navigation context
+ navigationContextRef.current = null;
},
},
);
}
function markPostReplyStatus(postId: string, replyStatus: ManualReplyStatus) {
+ // Capture navigation context before mutation
+ const currentIndex = posts.findIndex((post) => post.id === postId);
+
+ navigationContextRef.current = {
+ currentIndex,
+ postsSnapshot: [...posts], // Create a snapshot
+ };
+
markPostReplyStatusMutation.mutate(
{
postId,
@@ -146,9 +270,21 @@ export function PostsProvider({
replyStatus,
},
{
+ onError() {
+ // Clear navigation context on error
+ navigationContextRef.current = null;
+ },
onSuccess() {
+ // First, invalidate and refresh the data
utils.socialPosts.getPosts.invalidate();
+
+ // Handle auto-navigation if needed
+ handleAutoNavigation(postId, 'reply');
+
router.refresh();
+
+ // Clear navigation context
+ navigationContextRef.current = null;
},
},
);
@@ -188,3 +324,9 @@ export function usePostsContext() {
return context;
}
+
+export function useOptionalPostsContext() {
+ const context = useContext(PostsContext);
+
+ return context; // Returns undefined if no provider, doesn't throw
+}