/** What the selected node is, in the order an investigator reads it. * * The panel deliberately repeats the case page's language: the reason comes * from fusion/explain.py through the entity endpoint, and IP associations are * worded "associated with", never "belongs to". */ import { useEffect, useState } from "react"; import { Link } from "react-router-dom"; import { api } from "../../api/client"; import type { EntityDetail } from "../../api/types"; import { formatId } from "../../lib/formatId"; import { RiskMeter } from "../ui"; import type { NodeData } from "./model"; export function SidePanel({ node, onClose, onExpand, onTrace, onHide, onPin, onTaintSeed, onCopy, pinned, }: { node: NodeData; onClose: () => void; onExpand: (id: string) => void; onTrace: (id: string, direction: "forward" | "backward") => void; onHide: (id: string) => void; onPin: (id: string) => void; onTaintSeed: (id: string) => void; onCopy: (value: string) => void; pinned: boolean; }) { const [detail, setDetail] = useState(null); const [error, setError] = useState(null); const entityId = (node.entity_id as string | undefined) ?? node.id; useEffect(() => { setDetail(null); setError(null); if (node.type !== "wallet") return; const controller = new AbortController(); api .entity(entityId, controller.signal) .then(setDetail) .catch((e: Error) => { if (!controller.signal.aborted) setError(e.message); }); return () => controller.abort(); }, [entityId, node.type]); return ( ); }