[socialmon] inbox: fix unusable prev/next buttons (#1572)
This commit is contained in:
parent
37210da6ce
commit
d7efb99636
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -76,13 +76,6 @@ export default function PostList() {
|
|||
All
|
||||
</Tabs.Tab>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label={<ShortcutDisplay action={ShortcutAction.GO_TO_PENDING} />}
|
||||
withArrow={true}>
|
||||
<Tabs.Tab fw={500} value="PENDING">
|
||||
Pending
|
||||
</Tabs.Tab>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label={<ShortcutDisplay action={ShortcutAction.GO_TO_REPLIED} />}
|
||||
withArrow={true}>
|
||||
|
|
@ -99,6 +92,13 @@ export default function PostList() {
|
|||
Irrelevant
|
||||
</Tabs.Tab>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label={<ShortcutDisplay action={ShortcutAction.GO_TO_PENDING} />}
|
||||
withArrow={true}>
|
||||
<Tabs.Tab fw={500} value="PENDING">
|
||||
Pending
|
||||
</Tabs.Tab>
|
||||
</Tooltip>
|
||||
</Tabs.List>
|
||||
</Tabs>
|
||||
<div className="absolute right-2 top-2 flex items-center gap-2">
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<QueriedRedditPost>;
|
||||
} | null>(null);
|
||||
|
||||
// Helper function to find the next logical post after a state change
|
||||
const findNextLogicalPost = (
|
||||
currentPostId: string,
|
||||
postsSnapshot: Array<QueriedRedditPost>,
|
||||
): 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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue