'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: () => (
Loading flow editor...
), }) 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 (
Loading...
) } return (
{/* Toolbar */}
rF
rFunds | Your Space
{/* 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) => (
))}
)}
)}
) }