"use client"; import { useEffect, useMemo, useState } from "react"; import { useParams, useRouter, useSearchParams } from "next/navigation"; import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs"; import { X } from "lucide-react"; import { ApiError } from "@/api/mutator"; import { type getBoardApiV1BoardsBoardIdGetResponse, useGetBoardApiV1BoardsBoardIdGet, useUpdateBoardApiV1BoardsBoardIdPatch, } from "@/api/generated/boards/boards"; import { type listGatewaysApiV1GatewaysGetResponse, useListGatewaysApiV1GatewaysGet, } from "@/api/generated/gateways/gateways"; import type { BoardRead, BoardUpdate } from "@/api/generated/model"; import { BoardOnboardingChat } from "@/components/BoardOnboardingChat"; import { DashboardSidebar } from "@/components/organisms/DashboardSidebar"; import { DashboardShell } from "@/components/templates/DashboardShell"; import { Button } from "@/components/ui/button"; import { Dialog, DialogClose, DialogContent } from "@/components/ui/dialog"; 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"; const slugify = (value: string) => value .toLowerCase() .trim() .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 { isSignedIn } = useAuth(); const router = useRouter(); const searchParams = useSearchParams(); const params = useParams(); const boardIdParam = params?.boardId; const boardId = Array.isArray(boardIdParam) ? boardIdParam[0] : boardIdParam; const [board, setBoard] = useState(null); const [name, setName] = useState(undefined); const [gatewayId, setGatewayId] = useState(undefined); const [boardType, setBoardType] = useState(undefined); const [objective, setObjective] = useState(undefined); const [successMetrics, setSuccessMetrics] = useState( undefined, ); const [targetDate, setTargetDate] = useState(undefined); const [error, setError] = useState(null); const [metricsError, setMetricsError] = useState(null); const [isOnboardingOpen, setIsOnboardingOpen] = useState(false); const onboardingParam = searchParams.get("onboarding"); const searchParamsString = searchParams.toString(); const shouldAutoOpenOnboarding = onboardingParam !== null && onboardingParam !== "" && onboardingParam !== "0" && onboardingParam.toLowerCase() !== "false"; useEffect(() => { if (!boardId) return; if (!shouldAutoOpenOnboarding) return; setIsOnboardingOpen(true); // Remove the flag from the URL so refreshes don't constantly reopen it. const nextParams = new URLSearchParams(searchParamsString); nextParams.delete("onboarding"); const qs = nextParams.toString(); router.replace(qs ? `/boards/${boardId}/edit?${qs}` : `/boards/${boardId}/edit`); }, [boardId, router, searchParamsString, shouldAutoOpenOnboarding]); const gatewaysQuery = useListGatewaysApiV1GatewaysGet< listGatewaysApiV1GatewaysGetResponse, ApiError >(undefined, { query: { enabled: Boolean(isSignedIn), refetchOnMount: "always", retry: false, }, }); const boardQuery = useGetBoardApiV1BoardsBoardIdGet< getBoardApiV1BoardsBoardIdGetResponse, ApiError >(boardId ?? "", { query: { enabled: Boolean(isSignedIn && boardId), refetchOnMount: "always", retry: false, }, }); const updateBoardMutation = useUpdateBoardApiV1BoardsBoardIdPatch({ mutation: { onSuccess: (result) => { if (result.status === 200) { router.push(`/boards/${result.data.id}`); } }, onError: (err) => { setError(err.message || "Something went wrong."); }, }, }); const gateways = gatewaysQuery.data?.status === 200 ? gatewaysQuery.data.data.items ?? [] : []; const loadedBoard: BoardRead | null = boardQuery.data?.status === 200 ? boardQuery.data.data : null; const baseBoard = board ?? loadedBoard; const resolvedName = name ?? baseBoard?.name ?? ""; const resolvedGatewayId = gatewayId ?? baseBoard?.gateway_id ?? ""; const resolvedBoardType = boardType ?? baseBoard?.board_type ?? "goal"; const resolvedObjective = objective ?? baseBoard?.objective ?? ""; const resolvedSuccessMetrics = successMetrics ?? (baseBoard?.success_metrics ? JSON.stringify(baseBoard.success_metrics, null, 2) : ""); const resolvedTargetDate = targetDate ?? toDateInput(baseBoard?.target_date); const displayGatewayId = resolvedGatewayId || gateways[0]?.id || ""; const isLoading = gatewaysQuery.isLoading || boardQuery.isLoading || updateBoardMutation.isPending; const errorMessage = error ?? gatewaysQuery.error?.message ?? boardQuery.error?.message ?? null; const isFormReady = Boolean(resolvedName.trim() && displayGatewayId); const gatewayOptions = useMemo( () => gateways.map((gateway) => ({ value: gateway.id, label: gateway.name })), [gateways], ); const handleOnboardingConfirmed = (updated: BoardRead) => { setBoard(updated); setBoardType(updated.board_type ?? "goal"); setObjective(updated.objective ?? ""); setSuccessMetrics( updated.success_metrics ? JSON.stringify(updated.success_metrics, null, 2) : "", ); setTargetDate(toDateInput(updated.target_date)); setIsOnboardingOpen(false); }; const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); if (!isSignedIn || !boardId) return; const trimmedName = resolvedName.trim(); if (!trimmedName) { setError("Board name is required."); return; } const resolvedGatewayId = displayGatewayId; if (!resolvedGatewayId) { setError("Select a gateway before saving."); return; } setError(null); setMetricsError(null); let parsedMetrics: Record | null = null; if (resolvedSuccessMetrics.trim()) { try { parsedMetrics = JSON.parse(resolvedSuccessMetrics) as Record; } catch { setMetricsError("Success metrics must be valid JSON."); return; } } const payload: BoardUpdate = { name: trimmedName, slug: slugify(trimmedName), gateway_id: resolvedGatewayId || null, board_type: resolvedBoardType, objective: resolvedObjective.trim() || null, success_metrics: parsedMetrics, target_date: resolvedTargetDate ? new Date(resolvedTargetDate).toISOString() : null, }; updateBoardMutation.mutate({ boardId, data: payload }); }; return ( <>

Sign in to edit boards.

Edit board

Update board settings and gateway.

{resolvedBoardType !== "general" && baseBoard && !(baseBoard.goal_confirmed ?? false) ? (

Goal needs confirmation

Start onboarding to draft an objective and success metrics.

) : null}
setName(event.target.value)} placeholder="Board name" disabled={isLoading || !baseBoard} />
setTargetDate(event.target.value)} disabled={isLoading} />