👁️
1import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
2import { useState } from "react";
3import { type AtUri, asRkey } from "@/lib/atproto-client";
4import type { BacklinkRecord } from "@/lib/constellation-client";
5import {
6 directRepliesQueryOptions,
7 directReplyCountQueryOptions,
8} from "@/lib/constellation-queries";
9import type { SocialItemUri } from "@/lib/social-item-types";
10import { CommentItem } from "./CommentItem";
11
12interface CommentThreadProps {
13 backlink: BacklinkRecord;
14 type: "comment" | "reply";
15 subjectUri: SocialItemUri;
16 /** URI of the parent comment/reply (for deletion cache updates) */
17 parentUri?: AtUri;
18 depth?: number;
19}
20
21export function CommentThread({
22 backlink,
23 type,
24 subjectUri,
25 parentUri,
26 depth = 0,
27}: CommentThreadProps) {
28 const [showReplies, setShowReplies] = useState(depth < 2);
29
30 const did = backlink.did;
31 const rkey = asRkey(backlink.rkey);
32 const uri = `at://${did}/${backlink.collection}/${rkey}` satisfies AtUri;
33
34 const replyCountQuery = useQuery(directReplyCountQueryOptions(uri));
35 const replyCount = replyCountQuery.data ?? 0;
36
37 const repliesQuery = useInfiniteQuery({
38 ...directRepliesQueryOptions(uri),
39 enabled: showReplies,
40 });
41
42 const replies = repliesQuery.data?.pages.flatMap((p) => p.records) ?? [];
43 const hasReplies = replyCount > 0 || replies.length > 0;
44
45 return (
46 <div
47 className={
48 depth > 0 ? "pl-4 border-l border-gray-200 dark:border-zinc-600" : ""
49 }
50 >
51 <CommentItem
52 backlink={backlink}
53 type={type}
54 subjectUri={subjectUri}
55 parentUri={parentUri}
56 />
57
58 {hasReplies && !showReplies && (
59 <button
60 type="button"
61 onClick={() => setShowReplies(true)}
62 className="ml-4 text-sm text-blue-600 dark:text-blue-400 hover:underline"
63 >
64 {replyCount === 1 ? "Show 1 reply" : `Show ${replyCount} replies`}
65 </button>
66 )}
67
68 {showReplies && replies.length > 0 && (
69 <div className="mt-1">
70 {replies.map((reply) => (
71 <CommentThread
72 key={`${reply.did}/${reply.rkey}`}
73 backlink={reply}
74 type="reply"
75 subjectUri={subjectUri}
76 parentUri={uri}
77 depth={depth + 1}
78 />
79 ))}
80 </div>
81 )}
82
83 {showReplies && repliesQuery.hasNextPage && (
84 <button
85 type="button"
86 onClick={() => repliesQuery.fetchNextPage()}
87 disabled={repliesQuery.isFetchingNextPage}
88 className="ml-4 text-sm text-blue-600 dark:text-blue-400 hover:underline"
89 >
90 {repliesQuery.isFetchingNextPage ? "Loading..." : "Load more replies"}
91 </button>
92 )}
93 </div>
94 );
95}