diff --git a/frontend/src/app/boards/[boardId]/page.tsx b/frontend/src/app/boards/[boardId]/page.tsx index 6054726d..2e1b1b0c 100644 --- a/frontend/src/app/boards/[boardId]/page.tsx +++ b/frontend/src/app/boards/[boardId]/page.tsx @@ -6,11 +6,13 @@ import { useParams, useRouter } from "next/navigation"; import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs"; import { Activity, MessageSquare, Pencil, Settings, X } from "lucide-react"; import ReactMarkdown, { type Components } from "react-markdown"; +import remarkBreaks from "remark-breaks"; import remarkGfm from "remark-gfm"; import { DashboardSidebar } from "@/components/organisms/DashboardSidebar"; import { TaskBoard } from "@/components/organisms/TaskBoard"; import { DashboardShell } from "@/components/templates/DashboardShell"; +import { BoardChatComposer } from "@/components/BoardChatComposer"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -201,7 +203,6 @@ export default function BoardDetailPage() { ); const [isChatOpen, setIsChatOpen] = useState(false); const [chatMessages, setChatMessages] = useState([]); - const [chatInput, setChatInput] = useState(""); const [isChatSending, setIsChatSending] = useState(false); const [chatError, setChatError] = useState(null); const chatMessagesRef = useRef([]); @@ -235,11 +236,36 @@ export default function BoardDetailPage() { const [isSavingTask, setIsSavingTask] = useState(false); const [saveTaskError, setSaveTaskError] = useState(null); + const isSidePanelOpen = isDetailOpen || isChatOpen || isLiveFeedOpen; + const titleLabel = useMemo( () => (board ? `${board.name} board` : "Board"), [board], ); + useEffect(() => { + if (!isSidePanelOpen) return; + + const { body, documentElement } = document; + const originalHtmlOverflow = documentElement.style.overflow; + const originalBodyOverflow = body.style.overflow; + const originalBodyPaddingRight = body.style.paddingRight; + + const scrollbarWidth = window.innerWidth - documentElement.clientWidth; + + documentElement.style.overflow = "hidden"; + body.style.overflow = "hidden"; + if (scrollbarWidth > 0) { + body.style.paddingRight = `${scrollbarWidth}px`; + } + + return () => { + documentElement.style.overflow = originalHtmlOverflow; + body.style.overflow = originalBodyOverflow; + body.style.paddingRight = originalBodyPaddingRight; + }; + }, [isSidePanelOpen]); + const latestTaskTimestamp = (items: Task[]) => { let latestTime = 0; items.forEach((task) => { @@ -889,10 +915,10 @@ export default function BoardDetailPage() { } }; - const handleSendChat = async () => { - if (!isSignedIn || !boardId) return; - const trimmed = chatInput.trim(); - if (!trimmed) return; + const handleSendChat = useCallback(async (content: string): Promise => { + if (!isSignedIn || !boardId) return false; + const trimmed = content.trim(); + if (!trimmed) return false; setIsChatSending(true); setChatError(null); try { @@ -917,15 +943,16 @@ export default function BoardDetailPage() { return next; }); } - setChatInput(""); + return true; } catch (err) { setChatError( err instanceof Error ? err.message : "Unable to send message.", ); + return false; } finally { setIsChatSending(false); } - }; + }, [boardId, isSignedIn]); const assigneeById = useMemo(() => { const map = new Map(); @@ -1444,14 +1471,16 @@ export default function BoardDetailPage() { -
+
-
- {board?.name ?? "Board"} -

{board?.name ?? "Board"}

@@ -1724,7 +1753,7 @@ export default function BoardDetailPage() { ) : null}
{comment.message?.trim() ? ( -
+
(

), ul: ({ node: _node, ...props }) => (

    ), li: ({ node: _node, ...props }) => (
  • ), @@ -2021,10 +2050,10 @@ export default function BoardDetailPage() {
-
-
- {chatError ? ( -
+
+
+ {chatError ? ( +
{chatError}
) : null} @@ -2070,37 +2099,13 @@ export default function BoardDetailPage() {
)) - )} -
-
-
-