@@ -6,30 +6,59 @@ import { useSearchParams, useRouter } from "next/navigation";
66import { signIn , useSession } from "next-auth/react" ;
77import { api } from "@/server/trpc/react" ;
88import { FeedItemLoading } from "@/components/Feed" ;
9+ import { FilterPill , type Option } from "@/components/Feed/Filters" ;
910import { UnifiedContentCard } from "@/components/UnifiedContentCard" ;
1011import { useShellActions } from "@/components/Create/ShellActionsProvider" ;
1112
13+ type View = "all" | "following" ;
14+ type Sort = "recent" | "active" | "top" ;
15+
16+ const sortOptions : Option [ ] = [
17+ { value : "recent" , label : "Recent" } ,
18+ { value : "active" , label : "Active" } ,
19+ { value : "top" , label : "Top" } ,
20+ ] ;
21+
1222/**
1323 * Discussions — community threads (questions + discussions), same row style as
14- * the feed. Mirrors ui_kits/app/AppShell.jsx → Discussions.
24+ * the feed. All / Following tabs (deep-linkable via ?view=) + a sort filter,
25+ * mirroring the feed's tab + FilterPill pattern.
1526 */
1627const DiscussionsPage = ( ) => {
1728 const searchParams = useSearchParams ( ) ;
1829 const router = useRouter ( ) ;
1930 const { data : session } = useSession ( ) ;
2031 const { openCompose } = useShellActions ( ) ;
2132
22- const tag = searchParams ?. get ( "tag" ) || null ;
33+ const view : View =
34+ searchParams ?. get ( "view" ) === "following" ? "following" : "all" ;
35+ const sortParam = searchParams ?. get ( "sort" ) ;
36+ const sort : Sort =
37+ sortParam === "active" || sortParam === "top" ? sortParam : "recent" ;
38+
39+ // Build a ?view=&sort= query string, omitting defaults to keep URLs clean.
40+ const setParams = ( next : { view ?: View ; sort ?: Sort } ) => {
41+ const params = new URLSearchParams ( ) ;
42+ const nextView = next . view ?? view ;
43+ const nextSort = next . sort ?? sort ;
44+ if ( nextView === "following" ) params . set ( "view" , "following" ) ;
45+ if ( nextSort !== "recent" ) params . set ( "sort" , nextSort ) ;
46+ const qs = params . toString ( ) ;
47+ router . replace ( qs ? `/discussions?${ qs } ` : "/discussions" , {
48+ scroll : false ,
49+ } ) ;
50+ } ;
2351
2452 const { status, data, isFetchingNextPage, fetchNextPage, hasNextPage } =
25- api . content . getFeed . useInfiniteQuery (
26- { limit : 25 , sort : "recent" , kinds : [ "DISCUSSION" , "QUESTION" ] , tag } ,
27- { getNextPageParam : ( lastPage ) => lastPage . nextCursor } ,
53+ api . discussion . list . useInfiniteQuery (
54+ { limit : 25 , view, sort } ,
55+ {
56+ getNextPageParam : ( lastPage ) => lastPage . nextCursor ,
57+ // Following tab is meaningless signed-out — skip the query.
58+ enabled : view === "all" || ! ! session ,
59+ } ,
2860 ) ;
2961
30- const { data : popularData } = api . tag . getPopular . useQuery ( { limit : 6 } ) ;
31- const topics = popularData ?. data ?? [ ] ;
32-
3362 const { ref, inView } = useInView ( ) ;
3463 useEffect ( ( ) => {
3564 if ( inView && hasNextPage ) fetchNextPage ( ) ;
@@ -38,6 +67,13 @@ const DiscussionsPage = () => {
3867 const empty =
3968 status === "success" && data . pages . every ( ( p ) => p . items . length === 0 ) ;
4069
70+ const tabClass = ( active : boolean ) =>
71+ `whitespace-nowrap border-b-2 px-1 pb-2 text-sm font-semibold transition-colors ${
72+ active
73+ ? "border-accent text-fg"
74+ : "border-transparent text-muted hover:text-fg"
75+ } `;
76+
4177 return (
4278 < div >
4379 < div className = "flex flex-wrap items-end justify-between gap-4" >
@@ -61,36 +97,42 @@ const DiscussionsPage = () => {
6197 loud with other builders working with AI.
6298 </ p >
6399
64- { topics . length > 0 && (
65- < div className = "mt-5 flex flex-wrap gap-2 " >
100+ < div className = "mt-6 flex items-center justify-between border-b border-hairline" >
101+ < div className = "flex items-center gap-5" role = "tablist ">
66102 < button
67- onClick = { ( ) => router . push ( "/discussions" ) }
68- className = { `whitespace-nowrap rounded-full px-3 py-1 font-mono text-xs transition-colors ${
69- ! tag
70- ? "bg-accent text-on-accent"
71- : "border border-hairline text-muted hover:text-fg"
72- } `}
103+ role = "tab"
104+ aria-selected = { view === "all" }
105+ data-testid = "discussions-tab-all"
106+ onClick = { ( ) => setParams ( { view : "all" } ) }
107+ className = { tabClass ( view === "all" ) }
73108 >
74109 All
75110 </ button >
76- { topics . map ( ( t ) => {
77- const on = tag === t . slug ;
78- return (
79- < button
80- key = { t . slug }
81- onClick = { ( ) => router . push ( `/discussions?tag=${ t . slug } ` ) }
82- className = { `whitespace-nowrap rounded-full px-3 py-1 font-mono text-xs transition-colors ${
83- on
84- ? "bg-accent text-on-accent"
85- : "border border-hairline text-muted hover:text-fg"
86- } `}
87- >
88- { t . title }
89- </ button >
90- ) ;
91- } ) }
111+ < button
112+ role = "tab"
113+ aria-selected = { view === "following" }
114+ data-testid = "discussions-tab-following"
115+ onClick = { ( ) => {
116+ if ( ! session ) return signIn ( ) ;
117+ setParams ( { view : "following" } ) ;
118+ } }
119+ className = { tabClass ( view === "following" ) }
120+ >
121+ Following
122+ </ button >
123+ </ div >
124+ < div className = "pb-1" >
125+ < FilterPill
126+ testId = "discussions-sort"
127+ label = "Sort discussions"
128+ value = { sort }
129+ options = { sortOptions }
130+ isDefault = { sort === "recent" }
131+ align = "right"
132+ onChange = { ( next ) => setParams ( { sort : next as Sort } ) }
133+ />
92134 </ div >
93- ) }
135+ </ div >
94136
95137 < section className = "mt-6 space-y-3" >
96138 { status === "pending" &&
@@ -134,9 +176,17 @@ const DiscussionsPage = () => {
134176 </ Fragment >
135177 ) ) }
136178
137- { empty && (
179+ { empty && view === "following" && (
180+ < div className = "rounded-lg border border-dashed border-hairline p-12 text-center font-mono text-sm text-faint" >
181+ { "// " } you're not following any discussions yet — open a thread
182+ and hit Follow
183+ </ div >
184+ ) }
185+
186+ { empty && view === "all" && (
138187 < div className = "rounded-lg border border-dashed border-hairline p-12 text-center font-mono text-sm text-faint" >
139- { "// " } nothing here yet — { session ?. user ? "start one" : "sign in to start one" }
188+ { "// " } nothing here yet —{ " " }
189+ { session ?. user ? "start one" : "sign in to start one" }
140190 </ div >
141191 ) }
142192
0 commit comments