"use client"; /** * use-whiteboard — T-168 canvas query + save for a whiteboard PAGE. * * The canvas document is the full `@factory/whiteboard` v1 shape; the server * re-validates it on every save (400 with field-level issues otherwise). The * save mutation is OPTIMISTIC: the renderer already applied the change * locally (canvas ops are immutable), so the mutation only reconciles the * server's `updatedAt` — on failure the canvas is re-fetched (the server's * truth wins; single-user v1 means there is nobody to merge with). */ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { whiteboardApi, type WhiteboardCanvasResponse } from "@/lib/api"; import type { Canvas } from "@factory/whiteboard"; export const whiteboardKey = (pageId: string) => ["pages", pageId, "whiteboard"] as const; /** `GET /pages/:id/whiteboard` — 404 → the page has no canvas (non-whiteboard). */ export function useWhiteboardCanvas(pageId: string) { return useQuery({ queryKey: whiteboardKey(pageId), queryFn: () => whiteboardApi.get(pageId), enabled: pageId.length > 0 }); } /** `PUT /pages/:id/whiteboard` — optimistic full-canvas save. */ export function useWhiteboardSave(pageId: string) { const qc = useQueryClient(); return useMutation({ mutationFn: (canvas: Canvas) => whiteboardApi.save(pageId, canvas), onMutate: async (canvas) => { // The caller has already put `canvas` in local state; mark the query // fresh so an in-flight refetch doesn't clobber it while saving. await qc.cancelQueries({ queryKey: whiteboardKey(pageId) }); const prev = qc.getQueryData(whiteboardKey(pageId)); if (prev) qc.setQueryData(whiteboardKey(pageId), { ...prev, canvas, updatedAt: prev.updatedAt }); return { prev }; }, onError: (_err, _canvas, ctx) => { // Single-user v1: the server's stored document is the truth — revert. if (ctx?.prev) qc.setQueryData(whiteboardKey(pageId), ctx.prev); else qc.invalidateQueries({ queryKey: whiteboardKey(pageId) }); }, onSuccess: (res) => { // Keep the renderer's canvas (it already matches the saved document) — // only sync the server's updatedAt (drives the "Saved" indicator). A // full refetch here could clobber a newer local edit. qc.setQueryData(whiteboardKey(pageId), (prev) => prev ? { ...prev, updatedAt: res.updatedAt } : prev ); } }); }