import { useEffect, useState, useCallback } from 'react' import { useParams, useNavigate, useBlocker } from 'react-router-dom' import { useStore } from 'zustand' import { Undo2, Redo2, Save, CheckCircle2, Monitor } from 'lucide-react' import { treesApi } from '@/api' import type { TreeCreate, TreeUpdate } from '@/types' import { useTreeEditorStore, useTreeEditorTemporal } from '@/store/treeEditorStore' import { TreeEditorLayout } from '@/components/tree-editor/TreeEditorLayout' import { ValidationSummary } from '@/components/tree-editor/ValidationSummary' import { useKeyboardShortcuts } from '@/hooks/useKeyboardShortcuts' import { usePermissions } from '@/hooks/usePermissions' import { cn } from '@/lib/utils' export function TreeEditorPage() { const { id } = useParams<{ id: string }>() const navigate = useNavigate() const isEditMode = !!id const { canCreateTrees, canEditTree } = usePermissions() const { name, isDirty, isLoading, isSaving, validationErrors, initNewTree, loadTree, loadDraft, discardDraft, reset, validate, getTreeForSave, markSaved, setLoading, setSaving, selectNode } = useTreeEditorStore() // Access undo/redo from temporal store const { undo, redo, pastStates, futureStates } = useStore(useTreeEditorTemporal) const [showDraftPrompt, setShowDraftPrompt] = useState(false) const [saveError, setSaveError] = useState(null) // Mobile detection const [isMobile, setIsMobile] = useState(false) useEffect(() => { const check = () => setIsMobile(window.innerWidth < 768) check() window.addEventListener('resize', check) return () => window.removeEventListener('resize', check) }, []) // Calculate if there are blocking errors const hasBlockingErrors = validationErrors.some(e => e.severity === 'error') // Block navigation if there are unsaved changes const blocker = useBlocker( ({ currentLocation, nextLocation }) => isDirty && currentLocation.pathname !== nextLocation.pathname ) // Keyboard shortcuts for undo/redo/save useKeyboardShortcuts([ { key: 'z', ctrl: true, handler: () => { if (pastStates.length > 0) undo() } }, { key: 'z', ctrl: true, shift: true, handler: () => { if (futureStates.length > 0) redo() } }, { key: 's', ctrl: true, handler: () => { handleSave() } } ]) // Permission guard: redirect viewers away from editor useEffect(() => { if (!canCreateTrees) { navigate('/trees') } }, [canCreateTrees, navigate]) // Initialize or load tree useEffect(() => { if (!canCreateTrees) return const initialize = async () => { if (isEditMode) { setLoading(true) try { const tree = await treesApi.get(id) if (!canEditTree({ author_id: tree.author_id, account_id: tree.account_id })) { navigate('/trees') return } loadTree(tree) } catch (err) { console.error('Failed to load tree:', err) navigate('/trees') } } else { initNewTree() // Check for draft after initializing const draftExists = localStorage.getItem('tree-editor-draft') !== null if (draftExists) { setShowDraftPrompt(true) } } } initialize() return () => { reset() } }, [id, isEditMode, canCreateTrees]) // Handle unsaved changes warning useEffect(() => { const handleBeforeUnload = (e: BeforeUnloadEvent) => { if (isDirty) { e.preventDefault() e.returnValue = '' } } window.addEventListener('beforeunload', handleBeforeUnload) return () => window.removeEventListener('beforeunload', handleBeforeUnload) }, [isDirty]) const handleRestoreDraft = () => { loadDraft() setShowDraftPrompt(false) } const handleDiscardDraft = () => { discardDraft() setShowDraftPrompt(false) } const handleManualValidate = () => { validate() } const handleSelectNode = (nodeId: string) => { selectNode(nodeId) } const handleSave = useCallback(async () => { setSaveError(null) // Validate first const errors = validate() const hasErrors = errors.some(e => e.severity === 'error') if (hasErrors) { setSaveError('Please fix validation errors before saving') return } setSaving(true) try { const treeData = getTreeForSave() if (isEditMode) { await treesApi.update(id!, treeData as TreeUpdate) markSaved() } else { const newTree = await treesApi.create(treeData as TreeCreate) // Mark saved BEFORE navigating to avoid triggering the blocker markSaved() // Navigate to edit mode with the new ID navigate(`/trees/${newTree.id}/edit`, { replace: true }) } } catch (err) { console.error('Failed to save tree:', err) setSaveError('Failed to save tree. Please try again.') } finally { setSaving(false) } }, [isEditMode, id, validate, getTreeForSave, markSaved, navigate]) // Handle blocker const handleBlockerProceed = () => { if (blocker.state === 'blocked') { blocker.proceed() } } const handleBlockerReset = () => { if (blocker.state === 'blocked') { blocker.reset() } } if (isLoading) { return (
) } // Mobile gate: show read-only message if (isMobile) { return (

Desktop Required

The tree editor requires a larger screen for the best experience. Please open this page on a desktop or tablet in landscape mode.

) } return (
{/* Draft Restore Prompt */} {showDraftPrompt && (

Restore Draft?

You have an unsaved draft from a previous session. Would you like to restore it?

)} {/* Unsaved Changes Dialog */} {blocker.state === 'blocked' && (

Unsaved Changes

You have unsaved changes. Are you sure you want to leave?

)} {/* Toolbar */}

{isEditMode ? 'Edit Tree' : 'Create New Tree'} {name && - {name}}

{isDirty && ( Unsaved )}
{/* Undo/Redo */}
{/* Validate */} {/* Save */}
{/* Error Display */} {saveError && (
{saveError}
)} {/* Validation Summary */} {validationErrors.length > 0 && (
)} {/* Main Editor */}
) } export default TreeEditorPage