'use client' import dynamic from 'next/dynamic' import { useState, useEffect, useRef } from 'react' import { toast } from 'sonner' import type { ExcalidrawElement } from '@excalidraw/excalidraw/types/element/types' import type { AppState, BinaryFiles } from '@excalidraw/excalidraw/types/types' import '@excalidraw/excalidraw/index.css' // Dynamic import with SSR disabled is REQUIRED for Excalidraw due to window dependencies const Excalidraw = dynamic( async () => (await import('@excalidraw/excalidraw')).Excalidraw, { ssr: false } ) interface CanvasBoardProps { initialData?: string canvasId?: string name: string } export function CanvasBoard({ initialData, canvasId, name }: CanvasBoardProps) { const [isDarkMode, setIsDarkMode] = useState(false) const [saveStatus, setSaveStatus] = useState<'saved' | 'saving' | 'error'>('saved') const saveTimeoutRef = useRef(null) const filesRef = useRef({}) // Parse initial state safely (ONLY ON MOUNT to prevent Next.js revalidation infinite loops) const [elements] = useState(() => { if (initialData) { try { const parsed = JSON.parse(initialData) if (parsed && Array.isArray(parsed)) { return parsed } else if (parsed && parsed.elements) { // Restore binary files if present if (parsed.files && typeof parsed.files === 'object') { filesRef.current = parsed.files } return parsed.elements } } catch (e) { console.error("[CanvasBoard] Failed to parse initial Excalidraw data:", e) } } return [] }) // Detect dark mode from html class useEffect(() => { const checkDarkMode = () => { const isDark = document.documentElement.classList.contains('dark') setIsDarkMode(isDark) } checkDarkMode() // Observer for theme changes const observer = new MutationObserver(checkDarkMode) observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }) return () => observer.disconnect() }, []) const handleChange = ( excalidrawElements: readonly ExcalidrawElement[], appState: AppState, files: BinaryFiles ) => { // Keep files ref up to date so we can include them in the save payload if (files) filesRef.current = files if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current) setSaveStatus('saving') saveTimeoutRef.current = setTimeout(async () => { try { // Save both elements and binary files so images persist across page changes const snapshot = JSON.stringify({ elements: excalidrawElements, files: filesRef.current }) await fetch('/api/canvas', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: canvasId || null, name, data: snapshot }) }) setSaveStatus('saved') } catch (e) { console.error("[CanvasBoard] Save failure:", e) setSaveStatus('error') } }, 2000) } return (
) }