synor/apps/explorer-web/src/components/BlockRelationshipDiagram.tsx
Gulshan Yadav 48949ebb3f Initial commit: Synor blockchain monorepo
A complete blockchain implementation featuring:
- synord: Full node with GHOSTDAG consensus
- explorer-web: Modern React blockchain explorer with 3D DAG visualization
- CLI wallet and tools
- Smart contract SDK and example contracts (DEX, NFT, token)
- WASM crypto library for browser/mobile
2026-01-08 05:22:17 +05:30

195 lines
6.7 KiB
TypeScript

/**
* Visual mini-DAG diagram showing block relationships.
* Displays parent blocks → current block → child blocks.
*/
import { Link } from 'react-router-dom';
import { ArrowDown, Box, Layers } from 'lucide-react';
import { truncateHash } from '../lib/utils';
import { cn } from '../lib/utils';
interface BlockRelationshipDiagramProps {
currentHash: string;
parentHashes: string[];
childrenHashes: string[];
isChainBlock?: boolean;
mergeSetBlues?: string[];
mergeSetReds?: string[];
}
export default function BlockRelationshipDiagram({
currentHash,
parentHashes,
childrenHashes,
isChainBlock = true,
mergeSetBlues = [],
mergeSetReds = [],
}: BlockRelationshipDiagramProps) {
// Determine selected parent (first one by convention)
const selectedParent = parentHashes[0];
const otherParents = parentHashes.slice(1);
return (
<div className="relative overflow-hidden rounded-2xl border border-gray-700/50 bg-gray-900/40 backdrop-blur-xl p-6">
{/* Background gradient */}
<div className="absolute inset-0 bg-gradient-to-br from-synor-500/5 via-transparent to-violet-500/5" />
<div className="relative">
{/* Header */}
<div className="flex items-center gap-2 mb-6">
<Layers size={18} className="text-synor-400" />
<h3 className="font-semibold">Block Relationships</h3>
</div>
<div className="flex flex-col items-center gap-3">
{/* Parent Blocks */}
{parentHashes.length > 0 && (
<>
<div className="flex items-center gap-3 flex-wrap justify-center">
{selectedParent && (
<BlockNode
hash={selectedParent}
type="parent"
isSelected
label="Selected Parent"
/>
)}
{otherParents.map((hash) => (
<BlockNode
key={hash}
hash={hash}
type="parent"
label="Parent"
/>
))}
</div>
{/* Arrow down */}
<div className="flex flex-col items-center">
<div className="w-px h-4 bg-gradient-to-b from-synor-500/50 to-synor-500" />
<ArrowDown size={16} className="text-synor-400 -mt-1" />
</div>
</>
)}
{/* Current Block - Highlighted */}
<div className="relative">
{/* Glow effect */}
<div className="absolute inset-0 -m-2 rounded-2xl bg-synor-500/20 blur-xl" />
<div
className={cn(
'relative px-6 py-4 rounded-xl border-2 bg-gray-800/80 backdrop-blur',
isChainBlock
? 'border-synor-500 shadow-[0_0_30px_rgba(124,58,237,0.3)]'
: 'border-blue-500 shadow-[0_0_30px_rgba(59,130,246,0.3)]'
)}
>
<div className="flex items-center gap-3">
<div
className={cn(
'p-2 rounded-lg',
isChainBlock ? 'bg-synor-500/20' : 'bg-blue-500/20'
)}
>
<Box
size={24}
className={isChainBlock ? 'text-synor-400' : 'text-blue-400'}
/>
</div>
<div>
<div className="text-xs text-gray-400 mb-0.5">Current Block</div>
<div className="font-mono text-sm font-medium">
{truncateHash(currentHash, 8, 8)}
</div>
</div>
</div>
{/* Chain block indicator */}
{isChainBlock && (
<div className="absolute -top-2 -right-2 px-2 py-0.5 bg-synor-500 text-white text-xs font-medium rounded-full">
Chain
</div>
)}
</div>
</div>
{/* Arrow down to children */}
{childrenHashes.length > 0 && (
<>
<div className="flex flex-col items-center">
<ArrowDown size={16} className="text-synor-400" />
<div className="w-px h-4 bg-gradient-to-b from-synor-500 to-synor-500/50" />
</div>
{/* Child Blocks */}
<div className="flex items-center gap-3 flex-wrap justify-center">
{childrenHashes.map((hash) => (
<BlockNode key={hash} hash={hash} type="child" label="Child" />
))}
</div>
</>
)}
{/* Merge set info */}
{(mergeSetBlues.length > 0 || mergeSetReds.length > 0) && (
<div className="mt-4 pt-4 border-t border-gray-700/50 w-full">
<div className="flex items-center gap-4 justify-center text-sm">
{mergeSetBlues.length > 0 && (
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-blue-500" />
<span className="text-gray-400">
{mergeSetBlues.length} blue merge
</span>
</div>
)}
{mergeSetReds.length > 0 && (
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-red-500" />
<span className="text-gray-400">
{mergeSetReds.length} red merge
</span>
</div>
)}
</div>
</div>
)}
</div>
</div>
</div>
);
}
interface BlockNodeProps {
hash: string;
type: 'parent' | 'child';
isSelected?: boolean;
label: string;
}
function BlockNode({ hash, type, isSelected, label }: BlockNodeProps) {
return (
<Link
to={`/block/${hash}`}
className={cn(
'group relative px-4 py-2.5 rounded-lg border transition-all duration-200',
'hover:scale-105 hover:shadow-lg',
isSelected
? 'bg-amber-900/30 border-amber-600/50 hover:border-amber-500'
: type === 'parent'
? 'bg-gray-800/50 border-gray-600/50 hover:border-gray-500'
: 'bg-gray-800/50 border-gray-600/50 hover:border-gray-500'
)}
>
<div className="text-xs text-gray-500 mb-0.5">{label}</div>
<div className="font-mono text-sm text-gray-300 group-hover:text-white transition-colors">
{truncateHash(hash, 6, 6)}
</div>
{/* Selected indicator */}
{isSelected && (
<div className="absolute -top-1.5 -right-1.5 w-3 h-3 rounded-full bg-amber-500 border-2 border-gray-900" />
)}
</Link>
);
}