"use client"; import { useEffect, useMemo, useState } from "react"; import { useParams, useRouter } from "next/navigation"; import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs"; import { DashboardSidebar } from "@/components/organisms/DashboardSidebar"; import { TaskBoard } from "@/components/organisms/TaskBoard"; import { DashboardShell } from "@/components/templates/DashboardShell"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; type Board = { id: string; name: string; slug: string; gateway_url?: string | null; gateway_token?: string | null; gateway_main_session_key?: string | null; gateway_workspace_root?: string | null; }; type Task = { id: string; title: string; description?: string | null; status: string; priority: string; due_at?: string | null; }; const apiBase = process.env.NEXT_PUBLIC_API_URL?.replace(/\/+$/, "") || "http://localhost:8000"; const priorities = [ { value: "low", label: "Low" }, { value: "medium", label: "Medium" }, { value: "high", label: "High" }, ]; export default function BoardDetailPage() { const router = useRouter(); const params = useParams(); const boardIdParam = params?.boardId; const boardId = Array.isArray(boardIdParam) ? boardIdParam[0] : boardIdParam; const { getToken, isSignedIn } = useAuth(); const [board, setBoard] = useState(null); const [tasks, setTasks] = useState([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [isDialogOpen, setIsDialogOpen] = useState(false); const [title, setTitle] = useState(""); const [description, setDescription] = useState(""); const [priority, setPriority] = useState("medium"); const [createError, setCreateError] = useState(null); const [isCreating, setIsCreating] = useState(false); const [gatewayUrl, setGatewayUrl] = useState(""); const [gatewayToken, setGatewayToken] = useState(""); const [gatewayMainSessionKey, setGatewayMainSessionKey] = useState(""); const [gatewayWorkspaceRoot, setGatewayWorkspaceRoot] = useState(""); const [isSaving, setIsSaving] = useState(false); const [saveError, setSaveError] = useState(null); const [saveSuccess, setSaveSuccess] = useState(false); const titleLabel = useMemo( () => (board ? `${board.name} board` : "Board"), [board], ); const loadBoard = async () => { if (!isSignedIn || !boardId) return; setIsLoading(true); setError(null); try { const token = await getToken(); const [boardResponse, tasksResponse] = await Promise.all([ fetch(`${apiBase}/api/v1/boards/${boardId}`, { headers: { Authorization: token ? `Bearer ${token}` : "", }, }), fetch(`${apiBase}/api/v1/boards/${boardId}/tasks`, { headers: { Authorization: token ? `Bearer ${token}` : "", }, }), ]); if (!boardResponse.ok) { throw new Error("Unable to load board."); } if (!tasksResponse.ok) { throw new Error("Unable to load tasks."); } const boardData = (await boardResponse.json()) as Board; const taskData = (await tasksResponse.json()) as Task[]; setBoard(boardData); setTasks(taskData); setGatewayUrl(boardData.gateway_url ?? ""); setGatewayMainSessionKey(boardData.gateway_main_session_key ?? ""); setGatewayWorkspaceRoot(boardData.gateway_workspace_root ?? ""); } catch (err) { setError(err instanceof Error ? err.message : "Something went wrong."); } finally { setIsLoading(false); } }; useEffect(() => { loadBoard(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [boardId, isSignedIn]); const resetForm = () => { setTitle(""); setDescription(""); setPriority("medium"); setCreateError(null); }; const handleCreateTask = async () => { if (!isSignedIn || !boardId) return; const trimmed = title.trim(); if (!trimmed) { setCreateError("Add a task title to continue."); return; } setIsCreating(true); setCreateError(null); try { const token = await getToken(); const response = await fetch(`${apiBase}/api/v1/boards/${boardId}/tasks`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: token ? `Bearer ${token}` : "", }, body: JSON.stringify({ title: trimmed, description: description.trim() || null, status: "inbox", priority, }), }); if (!response.ok) { throw new Error("Unable to create task."); } const created = (await response.json()) as Task; setTasks((prev) => [created, ...prev]); setIsDialogOpen(false); resetForm(); } catch (err) { setCreateError(err instanceof Error ? err.message : "Something went wrong."); } finally { setIsCreating(false); } }; const handleSaveSettings = async () => { if (!isSignedIn || !boardId) return; setIsSaving(true); setSaveError(null); setSaveSuccess(false); try { const token = await getToken(); const payload: Partial = { gateway_url: gatewayUrl.trim() || null, gateway_main_session_key: gatewayMainSessionKey.trim() || null, gateway_workspace_root: gatewayWorkspaceRoot.trim() || null, }; if (gatewayToken.trim()) { payload.gateway_token = gatewayToken.trim(); } const response = await fetch(`${apiBase}/api/v1/boards/${boardId}`, { method: "PATCH", headers: { "Content-Type": "application/json", Authorization: token ? `Bearer ${token}` : "", }, body: JSON.stringify(payload), }); if (!response.ok) { throw new Error("Unable to update board settings."); } const updated = (await response.json()) as Board; setBoard(updated); setGatewayUrl(updated.gateway_url ?? ""); setGatewayMainSessionKey(updated.gateway_main_session_key ?? ""); setGatewayWorkspaceRoot(updated.gateway_workspace_root ?? ""); setGatewayToken(""); setSaveSuccess(true); setTimeout(() => setSaveSuccess(false), 2500); } catch (err) { setSaveError(err instanceof Error ? err.message : "Something went wrong."); } finally { setIsSaving(false); } }; return (

Sign in to view boards.

{board?.slug ?? "board"}

{board?.name ?? "Board"}

Keep tasks moving through your workflow.

{error && (
{error}
)} {isLoading ? (
Loading {titleLabel}…
) : ( <> setIsDialogOpen(true)} isCreateDisabled={isCreating} />

Gateway settings

Connect this board to an OpenClaw gateway.

Used when provisioning agents and checking gateway status for this board.

setGatewayUrl(event.target.value)} placeholder="ws://gateway:18789" />
setGatewayToken(event.target.value)} placeholder="Leave blank to keep current token" />
setGatewayMainSessionKey(event.target.value) } placeholder="agent:main:main" />
setGatewayWorkspaceRoot(event.target.value) } placeholder="~/.openclaw/workspaces" />
{saveError ? (
{saveError}
) : null} {saveSuccess ? (
Gateway settings saved.
) : null}
)}
{ setIsDialogOpen(nextOpen); if (!nextOpen) { resetForm(); } }} > New task Add a task to the inbox and triage it when you are ready.
setTitle(event.target.value)} placeholder="e.g. Prepare launch notes" />