1"use client";
2
3
22import { useEffect } from "react";
23import { X } from "lucide-react";
24import { useQueryClient } from "@tanstack/react-query";
25import { useWorkspaceMembers } from "@/hooks/use-workspace";
26import { useAuthStore } from "@/lib/auth-store";
27import { realtime } from "@/lib/realtime";
28import { useCommentPillStore } from "@/stores/comment-pill-store";
29import { CommentThread, type CommentAnchor } from "./comment-thread";
30
31export interface CommentPanelProps {
32 pageId: string;
33 workspaceId: string;
34 open: boolean;
35 onClose: () => void;
36}
37
38export function CommentPanel({ pageId, workspaceId, open, onClose }: CommentPanelProps) {
39 const qc = useQueryClient();
40 const currentUserId = useAuthStore((s) => s.user?.id ?? null);
41 const { data: members = [] } = useWorkspaceMembers(workspaceId);
42 const clearPill = useCommentPillStore((s) => s.clear);
43
44
45
46
47 const canComment = members.some((m) => m.userId === currentUserId);
48 const composerMembers = members.map((m) => m.user);
49
50
51
52
53
54
55 useEffect(() => {
56 if (!open) return;
57 const off = realtime.on("comment.event", (frame) => {
58 if (frame.type !== "comment.event" || frame.pageId !== pageId) return;
59
60
61 void qc.invalidateQueries({ queryKey: ["comments", pageId] });
62 });
63 return () => off();
64 }, [open, pageId, qc]);
65
66
67
68 useEffect(() => {
69 if (open) {
70 clearPill(pageId);
71 void qc.invalidateQueries({ queryKey: ["comments", pageId] });
72 }
73 }, [open, pageId, qc, clearPill]);
74
75 if (!open) return null;
76
77 const anchor: CommentAnchor = { pageId };
78 return (
79 <>
80 {}
81 <div
82 className="fixed inset-0 z-40 bg-black/20"
83 aria-hidden
84 onClick={onClose}
85 />
86 <aside
87 className="fixed inset-y-0 right-0 z-50 flex w-full max-w-md flex-col border-l border-border bg-background shadow-xl"
88 role="dialog"
89 aria-label="Comments"
90 data-testid="comment-panel"
91 >
92 <div className="flex shrink-0 items-center justify-between border-b border-border px-4 py-3">
93 <h2 className="text-sm font-semibold">Comments</h2>
94 <button
95 type="button"
96 className="rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground"
97 onClick={onClose}
98 aria-label="Close comments"
99 >
100 <X className="size-4" aria-hidden />
101 </button>
102 </div>
103 <div className="min-h-0 flex-1 overflow-y-auto p-4">
104 <CommentThread
105 anchor={anchor}
106 members={composerMembers}
107 canComment={canComment}
108 currentUserId={currentUserId}
109 workspaceId={workspaceId}
110 />
111 </div>
112 </aside>
113 </>
114 );
115}
116