'use client' import { memo, useState, useCallback, useMemo } from 'react' import { Handle, Position, useReactFlow } from '@xyflow/react' import type { NodeProps } from '@xyflow/react' import type { SourceNodeData } from '@/lib/types' import { useAuthStore } from '@/lib/auth' const CHAIN_OPTIONS = [ { id: 1, name: 'Ethereum' }, { id: 10, name: 'Optimism' }, { id: 100, name: 'Gnosis' }, { id: 137, name: 'Polygon' }, { id: 8453, name: 'Base' }, ] const CURRENCIES = ['USD', 'EUR', 'GBP'] type SourceType = SourceNodeData['sourceType'] const SOURCE_TYPE_META: Record = { card: { label: 'Credit Card', color: 'violet' }, safe_wallet: { label: 'Safe / Wallet', color: 'cyan' }, ridentity: { label: 'rIdentity', color: 'purple' }, unconfigured: { label: 'Unconfigured', color: 'slate' }, } function SourceTypeIcon({ type, className = 'w-4 h-4' }: { type: SourceType; className?: string }) { switch (type) { case 'card': return ( ) case 'safe_wallet': return ( ) case 'ridentity': return ( ) default: // unconfigured return ( ) } } function SourceNode({ data, selected, id }: NodeProps) { const nodeData = data as SourceNodeData const { label, sourceType, flowRate, targetAllocations = [] } = nodeData const [isEditing, setIsEditing] = useState(false) const { setNodes } = useReactFlow() const { isAuthenticated, did, login } = useAuthStore() // Edit form state const [editLabel, setEditLabel] = useState(label) const [editSourceType, setEditSourceType] = useState(sourceType) const [editFlowRate, setEditFlowRate] = useState(flowRate) const [editCurrency, setEditCurrency] = useState(nodeData.transakConfig?.fiatCurrency || 'USD') const [editDefaultAmount, setEditDefaultAmount] = useState(nodeData.transakConfig?.defaultAmount?.toString() || '') const [editWalletAddress, setEditWalletAddress] = useState(nodeData.walletAddress || '') const [editChainId, setEditChainId] = useState(nodeData.chainId || 1) const [editSafeAddress, setEditSafeAddress] = useState(nodeData.safeAddress || '') const meta = SOURCE_TYPE_META[sourceType] const isUnconfigured = sourceType === 'unconfigured' const handleDoubleClick = useCallback((e: React.MouseEvent) => { e.stopPropagation() setEditLabel(label) setEditSourceType(sourceType) setEditFlowRate(flowRate) setEditCurrency(nodeData.transakConfig?.fiatCurrency || 'USD') setEditDefaultAmount(nodeData.transakConfig?.defaultAmount?.toString() || '') setEditWalletAddress(nodeData.walletAddress || '') setEditChainId(nodeData.chainId || 1) setEditSafeAddress(nodeData.safeAddress || '') setIsEditing(true) }, [label, sourceType, flowRate, nodeData]) const handleSave = useCallback(() => { setNodes((nds) => nds.map((node) => { if (node.id !== id) return node const updated: SourceNodeData = { ...(node.data as SourceNodeData), label: editLabel || 'Source', sourceType: editSourceType, flowRate: editFlowRate, } if (editSourceType === 'card') { updated.transakConfig = { fiatCurrency: editCurrency, defaultAmount: editDefaultAmount ? parseFloat(editDefaultAmount) : undefined, } } if (editSourceType === 'safe_wallet') { updated.walletAddress = editWalletAddress updated.chainId = editChainId updated.safeAddress = editSafeAddress } if (editSourceType === 'ridentity') { updated.encryptIdUserId = did || undefined } return { ...node, data: updated } })) setIsEditing(false) }, [id, editLabel, editSourceType, editFlowRate, editCurrency, editDefaultAmount, editWalletAddress, editChainId, editSafeAddress, did, setNodes]) const handleClose = useCallback(() => { setIsEditing(false) }, []) // Allocation bar segments const allocTotal = useMemo(() => targetAllocations.reduce((s, a) => s + a.percentage, 0), [targetAllocations] ) return ( <>
{/* Header */}
{label} {meta.label}
{/* Body */}
{/* Flow rate */}
Flow Rate ${flowRate.toLocaleString()}/mo
{/* Allocation bars */} {targetAllocations.length > 0 && (
Allocations
{targetAllocations.map((alloc, i) => (
0 ? (alloc.percentage / allocTotal) * 100 : 0}%`, backgroundColor: alloc.color, }} title={`${alloc.percentage}% → ${alloc.targetId}`} /> ))}
{targetAllocations.map((alloc) => ( {alloc.percentage}% ))}
)} {/* Hint */} {isUnconfigured && (
Double-click to configure
)} {!isUnconfigured && targetAllocations.length === 0 && (
Drag from handle below to connect to funnels
)}
{/* Bottom handle - connects to funnels */}
{/* Edit Modal */} {isEditing && (
e.stopPropagation()} > {/* Header */}

Configure Source

{/* Source Type Picker */}
{(['card', 'safe_wallet', 'ridentity'] as SourceType[]).map((type) => { const typeMeta = SOURCE_TYPE_META[type] const isSelected = editSourceType === type return ( ) })}
{/* Label */}
setEditLabel(e.target.value)} className="w-full text-sm px-3 py-2 border border-slate-200 rounded-lg text-slate-800" placeholder="Source name..." />
{/* Flow Rate */}
setEditFlowRate(Number(e.target.value))} className="w-full text-sm px-3 py-2 border border-slate-200 rounded-lg text-slate-800 font-mono" min="0" step="100" />
{/* Type-specific config */} {editSourceType === 'card' && (
Card Settings (Transak) setEditDefaultAmount(e.target.value)} placeholder="100" className="w-full text-xs px-2 py-1.5 border border-slate-200 rounded text-slate-800" min="0" />
)} {editSourceType === 'safe_wallet' && (
Safe / Wallet Settings setEditWalletAddress(e.target.value)} placeholder="0x..." className="w-full text-xs px-2 py-1.5 border border-slate-200 rounded mb-2 text-slate-800 font-mono" /> setEditSafeAddress(e.target.value)} placeholder="0x..." className="w-full text-xs px-2 py-1.5 border border-slate-200 rounded mb-2 text-slate-800 font-mono" />
)} {editSourceType === 'ridentity' && (
rIdentity (EncryptID) {isAuthenticated ? (
Connected {did && ( {did.slice(0, 20)}... )}
) : ( )}
)} {/* Save / Cancel */}
)} ) } export default memo(SourceNode)