'use client'
import dynamic from 'next/dynamic'
import Link from 'next/link'
import { useState, useCallback, useEffect, useRef } from 'react'
import { starterNodes } from '@/lib/presets'
import { serializeState, deserializeState, saveToLocal, loadFromLocal, listSavedSpaces, deleteFromLocal } from '@/lib/state'
import type { FlowNode, SpaceConfig } from '@/lib/types'
const FlowCanvas = dynamic(() => import('@/components/FlowCanvas'), {
ssr: false,
loading: () => (
),
})
export default function SpacePage() {
const [currentNodes, setCurrentNodes] = useState(starterNodes)
const [spaceName, setSpaceName] = useState('')
const [showSaveDialog, setShowSaveDialog] = useState(false)
const [showLoadDialog, setShowLoadDialog] = useState(false)
const [savedSpaces, setSavedSpaces] = useState([])
const [copied, setCopied] = useState(false)
const [loaded, setLoaded] = useState(false)
const nodesRef = useRef(starterNodes)
// Load from URL hash on mount
useEffect(() => {
if (typeof window === 'undefined') return
const hash = window.location.hash.slice(1)
if (hash.startsWith('s=')) {
const compressed = hash.slice(2)
const state = deserializeState(compressed)
if (state) {
setCurrentNodes(state.nodes)
nodesRef.current = state.nodes
}
}
setLoaded(true)
}, [])
const handleNodesChange = useCallback((nodes: FlowNode[]) => {
nodesRef.current = nodes
}, [])
const handleShare = useCallback(() => {
const compressed = serializeState(nodesRef.current)
const url = `${window.location.origin}/space#s=${compressed}`
navigator.clipboard.writeText(url).then(() => {
setCopied(true)
setTimeout(() => setCopied(false), 2000)
})
}, [])
const handleSave = useCallback(() => {
if (!spaceName.trim()) return
saveToLocal(spaceName.trim(), nodesRef.current)
setShowSaveDialog(false)
setSpaceName('')
}, [spaceName])
const handleLoadOpen = useCallback(() => {
setSavedSpaces(listSavedSpaces())
setShowLoadDialog(true)
}, [])
const handleLoadSpace = useCallback((config: SpaceConfig) => {
setCurrentNodes(config.nodes)
nodesRef.current = config.nodes
setShowLoadDialog(false)
}, [])
const handleDeleteSpace = useCallback((name: string) => {
deleteFromLocal(name)
setSavedSpaces(listSavedSpaces())
}, [])
const handleReset = useCallback(() => {
if (confirm('Reset canvas to a single empty funnel? This cannot be undone.')) {
setCurrentNodes([...starterNodes])
nodesRef.current = [...starterNodes]
// Clear URL hash
window.history.replaceState(null, '', window.location.pathname)
}
}, [])
if (!loaded) {
return (
)
}
return (
{/* Toolbar */}
{/* Canvas */}
n.id))}
initialNodes={currentNodes}
mode="space"
onNodesChange={handleNodesChange}
/>
{/* Save Dialog */}
{showSaveDialog && (
setShowSaveDialog(false)}>
e.stopPropagation()}>
Save Space
setSpaceName(e.target.value)}
placeholder="Space name..."
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mb-4 text-slate-800"
autoFocus
onKeyDown={e => e.key === 'Enter' && handleSave()}
/>
)}
{/* Load Dialog */}
{showLoadDialog && (
setShowLoadDialog(false)}>
e.stopPropagation()}>
Load Space
{savedSpaces.length === 0 ? (
No saved spaces yet.
) : (
{savedSpaces.map((space) => (
))}
)}
)}
)
}