From 0a1f4392e878db0659561816d214022c1f3f9119 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Thu, 5 Feb 2026 14:59:33 +0530 Subject: [PATCH] feat: add board goal editor and onboarding chat --- .../src/app/boards/[boardId]/edit/page.tsx | 105 +++++++ frontend/src/app/boards/[boardId]/page.tsx | 69 ++++- frontend/src/components/BoardGoalPanel.tsx | 145 ++++++++++ .../src/components/BoardOnboardingChat.tsx | 266 ++++++++++++++++++ 4 files changed, 581 insertions(+), 4 deletions(-) create mode 100644 frontend/src/components/BoardGoalPanel.tsx create mode 100644 frontend/src/components/BoardOnboardingChat.tsx diff --git a/frontend/src/app/boards/[boardId]/edit/page.tsx b/frontend/src/app/boards/[boardId]/edit/page.tsx index ee7e63ec..7f1f8a8e 100644 --- a/frontend/src/app/boards/[boardId]/edit/page.tsx +++ b/frontend/src/app/boards/[boardId]/edit/page.tsx @@ -9,7 +9,15 @@ import { DashboardSidebar } from "@/components/organisms/DashboardSidebar"; import { DashboardShell } from "@/components/templates/DashboardShell"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import SearchableSelect from "@/components/ui/searchable-select"; +import { Textarea } from "@/components/ui/textarea"; import { getApiBaseUrl } from "@/lib/api-base"; const apiBase = getApiBaseUrl(); @@ -19,6 +27,10 @@ type Board = { name: string; slug: string; gateway_id?: string | null; + board_type?: string; + objective?: string | null; + success_metrics?: Record | null; + target_date?: string | null; }; type Gateway = { @@ -36,6 +48,13 @@ const slugify = (value: string) => .replace(/[^a-z0-9]+/g, "-") .replace(/(^-|-$)/g, "") || "board"; +const toDateInput = (value?: string | null) => { + if (!value) return ""; + const date = new Date(value); + if (Number.isNaN(date.getTime())) return ""; + return date.toISOString().slice(0, 10); +}; + export default function EditBoardPage() { const { getToken, isSignedIn } = useAuth(); const router = useRouter(); @@ -47,9 +66,14 @@ export default function EditBoardPage() { const [name, setName] = useState(""); const [gateways, setGateways] = useState([]); const [gatewayId, setGatewayId] = useState(""); + const [boardType, setBoardType] = useState("goal"); + const [objective, setObjective] = useState(""); + const [successMetrics, setSuccessMetrics] = useState(""); + const [targetDate, setTargetDate] = useState(""); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); + const [metricsError, setMetricsError] = useState(null); const isFormReady = Boolean(name.trim() && gatewayId); @@ -88,6 +112,12 @@ export default function EditBoardPage() { if (data.gateway_id) { setGatewayId(data.gateway_id); } + setBoardType(data.board_type ?? "goal"); + setObjective(data.objective ?? ""); + setSuccessMetrics( + data.success_metrics ? JSON.stringify(data.success_metrics, null, 2) : "" + ); + setTargetDate(toDateInput(data.target_date)); } catch (err) { setError(err instanceof Error ? err.message : "Something went wrong."); } @@ -126,8 +156,19 @@ export default function EditBoardPage() { setIsLoading(true); setError(null); + setMetricsError(null); try { const token = await getToken(); + let parsedMetrics: Record | null = null; + if (successMetrics.trim()) { + try { + parsedMetrics = JSON.parse(successMetrics) as Record; + } catch { + setMetricsError("Success metrics must be valid JSON."); + setIsLoading(false); + return; + } + } const response = await fetch(`${apiBase}/api/v1/boards/${boardId}`, { method: "PATCH", @@ -139,6 +180,10 @@ export default function EditBoardPage() { name: name.trim(), slug: slugify(name.trim()), gateway_id: gatewayId || null, + board_type: boardType, + objective: objective.trim() || null, + success_metrics: parsedMetrics, + target_date: targetDate ? new Date(targetDate).toISOString() : null, }), }); if (!response.ok) { @@ -219,6 +264,66 @@ export default function EditBoardPage() { +
+
+ + +
+
+ + setTargetDate(event.target.value)} + disabled={isLoading} + /> +
+
+ +
+ +