'use client' import { useCallback, useState, useEffect } from 'react' import { ReactFlow, Controls, Background, BackgroundVariant, useNodesState, useEdgesState, addEdge, Connection, MarkerType, Panel, } from '@xyflow/react' import '@xyflow/react/dist/style.css' import SourceNode from './nodes/SourceNode' import ThresholdNode from './nodes/ThresholdNode' import RecipientNode from './nodes/RecipientNode' import type { FlowNode, FlowEdge, SourceNodeData, ThresholdNodeData, RecipientNodeData } from '@/lib/types' const nodeTypes = { source: SourceNode, threshold: ThresholdNode, recipient: RecipientNode, } const initialNodes: FlowNode[] = [ { id: 'source-1', type: 'source', position: { x: 50, y: 150 }, data: { label: 'Treasury', balance: 75000, flowRate: 500, }, }, { id: 'threshold-1', type: 'threshold', position: { x: 350, y: 50 }, data: { label: 'Public Goods Gate', minThreshold: 10000, maxThreshold: 50000, currentValue: 32000, }, }, { id: 'threshold-2', type: 'threshold', position: { x: 350, y: 350 }, data: { label: 'Research Gate', minThreshold: 5000, maxThreshold: 30000, currentValue: 8500, }, }, { id: 'recipient-1', type: 'recipient', position: { x: 700, y: 50 }, data: { label: 'Project Alpha', received: 24500, target: 30000, }, }, { id: 'recipient-2', type: 'recipient', position: { x: 700, y: 250 }, data: { label: 'Project Beta', received: 8000, target: 25000, }, }, { id: 'recipient-3', type: 'recipient', position: { x: 700, y: 450 }, data: { label: 'Research Fund', received: 15000, target: 15000, }, }, ] const initialEdges: FlowEdge[] = [ { id: 'e1', source: 'source-1', target: 'threshold-1', animated: true, style: { stroke: '#3b82f6', strokeWidth: 2 }, markerEnd: { type: MarkerType.ArrowClosed, color: '#3b82f6' }, }, { id: 'e2', source: 'source-1', target: 'threshold-2', animated: true, style: { stroke: '#3b82f6', strokeWidth: 2 }, markerEnd: { type: MarkerType.ArrowClosed, color: '#3b82f6' }, }, { id: 'e3', source: 'threshold-1', target: 'recipient-1', animated: true, style: { stroke: '#a855f7', strokeWidth: 2 }, markerEnd: { type: MarkerType.ArrowClosed, color: '#a855f7' }, }, { id: 'e4', source: 'threshold-1', target: 'recipient-2', animated: true, style: { stroke: '#a855f7', strokeWidth: 2 }, markerEnd: { type: MarkerType.ArrowClosed, color: '#a855f7' }, }, { id: 'e5', source: 'threshold-2', target: 'recipient-3', animated: true, style: { stroke: '#a855f7', strokeWidth: 2 }, markerEnd: { type: MarkerType.ArrowClosed, color: '#a855f7' }, }, ] export default function FlowCanvas() { const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes) const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges) const [isSimulating, setIsSimulating] = useState(true) const onConnect = useCallback( (params: Connection) => setEdges((eds) => addEdge( { ...params, animated: true, style: { stroke: '#64748b', strokeWidth: 2 }, markerEnd: { type: MarkerType.ArrowClosed, color: '#64748b' }, }, eds ) ), [setEdges] ) // Simulation effect useEffect(() => { if (!isSimulating) return const interval = setInterval(() => { setNodes((nds) => nds.map((node) => { if (node.type === 'source') { const data = node.data as SourceNodeData return { ...node, data: { ...data, balance: Math.max(0, data.balance - data.flowRate / 3600), }, } } if (node.type === 'threshold') { const data = node.data as ThresholdNodeData const change = (Math.random() - 0.3) * 100 return { ...node, data: { ...data, currentValue: Math.max(0, Math.min(100000, data.currentValue + change)), }, } } if (node.type === 'recipient') { const data = node.data as RecipientNodeData if (data.received < data.target) { return { ...node, data: { ...data, received: Math.min(data.target, data.received + Math.random() * 50), }, } } } return node }) ) }, 1000) return () => clearInterval(interval) }, [isSimulating, setNodes]) return (
Drag nodes to rearrange • Connect nodes to create flows