'use client' import { memo, useState, useCallback } from 'react' import { Handle, Position, useReactFlow } from '@xyflow/react' import type { NodeProps } from '@xyflow/react' import type { SourceNodeData } from '@/lib/types' const SOURCE_COLORS = ['#10b981', '#14b8a6', '#06b6d4', '#0ea5e9'] function SourceNode({ data, selected, id }: NodeProps) { const nodeData = data as SourceNodeData const { label, flowRate, sourceType, targetAllocations = [] } = nodeData const { setNodes } = useReactFlow() const [isEditing, setIsEditing] = useState(false) const [editLabel, setEditLabel] = useState(label) const [editRate, setEditRate] = useState(String(flowRate)) const [editType, setEditType] = useState(sourceType) const handleDoubleClick = useCallback((e: React.MouseEvent) => { e.stopPropagation() setEditLabel(label) setEditRate(String(flowRate)) setEditType(sourceType) setIsEditing(true) }, [label, flowRate, sourceType]) const handleSave = useCallback(() => { setNodes((nds) => nds.map((node) => { if (node.id !== id) return node return { ...node, data: { ...(node.data as SourceNodeData), label: editLabel.trim() || 'Source', flowRate: Math.max(0, Number(editRate) || 0), sourceType: editType, }, } })) setIsEditing(false) }, [id, editLabel, editRate, editType, setNodes]) const typeLabels = { 'recurring': 'Recurring', 'one-time': 'One-time', 'treasury': 'Treasury', } const typeColors = { 'recurring': 'bg-emerald-100 text-emerald-700', 'one-time': 'bg-blue-100 text-blue-700', 'treasury': 'bg-violet-100 text-violet-700', } return ( <>
{/* Header */}
{label}
{/* Body */}
{typeLabels[sourceType]}
${flowRate.toLocaleString()} /mo
{/* Allocation bars */} {targetAllocations.length > 0 && (
Flow
{targetAllocations.map((alloc, idx) => (
))}
)} {/* Flow indicator */}
Outflow
Double-click to edit
{/* Bottom handle — connects to funnel top */}
{/* Edit Modal */} {isEditing && (
setIsEditing(false)} >
e.stopPropagation()} >

Edit Source

setEditLabel(e.target.value)} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm text-slate-800" autoFocus />
setEditRate(e.target.value)} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm text-slate-800 font-mono" min="0" />
)} ) } export default memo(SourceNode)