[redditmon] inbox: support subreddit filtering (#1664)

This commit is contained in:
Zhou Yuhang 2025-08-20 08:07:40 +08:00 committed by GitHub
parent afac121e9a
commit 056fe0d266
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 93 additions and 6 deletions

View File

@ -16,6 +16,7 @@ import { usePostsContext } from '~/components/posts/PostsContext';
import { ShortcutAction } from '~/config/shortcuts';
import type { PostListTab } from '~/types';
import SubredditFilter from '../SubredditFilter';
import FetchPostButton from './FetchPostButton';
import PostItem from './PostItem';
@ -43,7 +44,9 @@ export default function PostList() {
isLoading,
posts,
selectedPostId,
selectedSubreddits,
setActiveTab,
setSelectedSubreddits,
} = usePostsContext();
usePostTabShortcuts({ setActiveTab });
@ -120,6 +123,12 @@ export default function PostList() {
)}
<FetchPostButton />
</div>
<div className="px-2 pb-2">
<SubredditFilter
selectedSubreddits={selectedSubreddits}
onChange={setSelectedSubreddits}
/>
</div>
</div>
<div className="h-0 grow overflow-y-auto">
{isLoading ? (

View File

@ -40,8 +40,11 @@ type PostsContextType = {
posts: Array<QueriedRedditPost>;
// Selection state
selectedPostId: string | null;
// Subreddit filtering
selectedSubreddits: Array<string>;
setActiveTab: (tab: PostListTab) => void;
setSelectedPostId: (id: string | null) => void;
setSelectedSubreddits: (subreddits: Array<string>) => void;
};
const PostsContext = createContext<PostsContextType | undefined>(undefined);
@ -55,11 +58,15 @@ export function PostsProvider({
}) {
const [activeTab, setActiveTab] = useState<PostListTab>('ALL');
const [selectedPostId, setSelectedPostId] = useState<string | null>(null);
const [selectedSubreddits, setSelectedSubreddits] = useState<Array<string>>(
[],
);
const router = useRouter();
const params = useParams();
const utils = trpc.useUtils();
const prevActiveTabRef = useRef<PostListTab>(activeTab);
const prevSelectedSubredditsRef = useRef<Array<string>>(selectedSubreddits);
// Store navigation context before mutations to handle auto-navigation
const navigationContextRef = useRef<{
@ -147,7 +154,10 @@ export function PostsProvider({
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
trpc.socialPosts.getPosts.useInfiniteQuery(
{
filter: { tab: activeTab },
filter: {
subreddits: selectedSubreddits.length > 0 ? selectedSubreddits : undefined,
tab: activeTab
},
pagination: { limit: 20 },
projectSlug,
},
@ -173,10 +183,14 @@ export function PostsProvider({
useEffect(() => {
const hasTabChanged = prevActiveTabRef.current !== activeTab;
const hasSubredditFilterChanged =
JSON.stringify(prevSelectedSubredditsRef.current) !==
JSON.stringify(selectedSubreddits);
prevActiveTabRef.current = activeTab;
prevSelectedSubredditsRef.current = selectedSubreddits;
if (hasTabChanged) {
if (hasTabChanged || hasSubredditFilterChanged) {
setSelectedPostId(null);
return;
@ -188,8 +202,8 @@ export function PostsProvider({
setSelectedPostId(firstPostId);
router.push(`/projects/${projectSlug}/posts/${firstPostId}`);
}
}, [posts, selectedPostId, isLoading, projectSlug, router, activeTab]);
}, [posts, selectedPostId, isLoading, projectSlug, router, activeTab, selectedSubreddits]);
// Auto-load next page when user reaches the last post
useEffect(() => {
if (
@ -344,8 +358,10 @@ export function PostsProvider({
markPostReplyStatus,
posts,
selectedPostId,
selectedSubreddits,
setActiveTab,
setSelectedPostId,
setSelectedSubreddits,
};
return (

View File

@ -0,0 +1,54 @@
'use client';
import { MultiSelect } from '@mantine/core';
import { useMemo } from 'react';
import { trpc } from '~/hooks/trpc';
import useCurrentProjectSlug from '~/hooks/useCurrentProjectSlug';
type Props = Readonly<{
onChange: (selectedSubreddits: Array<string>) => void;
selectedSubreddits: Array<string>;
}>;
export default function SubredditFilter({
onChange,
selectedSubreddits,
}: Props) {
const projectSlug = useCurrentProjectSlug();
const { data: project, isLoading } = trpc.project.get.useQuery({
projectSlug,
});
const availableSubreddits = useMemo(() => {
console.info(project?.subredditKeywords);
const subreddits =
project?.subredditKeywords?.flatMap(
(group: { subreddits: Array<string> }) => group.subreddits,
) || [];
return subreddits.map((subreddit: string) => ({
label: `r/${subreddit}`,
value: `r/${subreddit}`,
}));
}, [project?.subredditKeywords]);
if (isLoading || availableSubreddits.length === 0) {
return null;
}
return (
<MultiSelect
clearable={true}
data={availableSubreddits}
placeholder="Filter by subreddits"
pt="md"
searchable={true}
size="sm"
value={selectedSubreddits}
w="100%"
onChange={onChange}
/>
);
}

View File

@ -92,6 +92,9 @@ export const projectRouter = router({
)
.query(async ({ input: { projectSlug } }) => {
return await prisma.project.findUnique({
include: {
subredditKeywords: true,
},
where: {
slug: projectSlug,
},

View File

@ -132,6 +132,7 @@ export const socialPostsRouter = router({
z.object({
cursor: z.string().nullish(),
filter: z.object({
subreddits: z.array(z.string()).optional(),
tab: z.enum(['ALL', 'PENDING', 'REPLIED', 'IRRELEVANT']),
}),
pagination: z.object({
@ -143,7 +144,7 @@ export const socialPostsRouter = router({
.query(async ({ input }) => {
const { cursor, filter, pagination, projectSlug } = input;
const { limit } = pagination;
const { tab } = filter;
const { subreddits, tab } = filter;
let postFilter = {};
@ -223,6 +224,11 @@ export const socialPostsRouter = router({
where: {
...postFilter,
projectId: project.id,
...(subreddits && subreddits.length > 0 && {
subreddit: {
in: subreddits,
},
}),
},
});

View File

@ -94,7 +94,6 @@ export type QueriedRedditPost = Omit<
| 'postId'
| 'projectId'
| 'response'
| 'response'
| 'updatedAt'
> & {
reply: RedditPostReply | null;