👁️
at main 95 lines 2.6 kB view raw
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}