'use client'; import { useState, useEffect, useRef } from 'react'; interface NodeConfig { id: string; label: string; description: string; icon: string; color: string; tech: string[]; x: number; y: number; connections: string[]; } const nodes: NodeConfig[] = [ { id: 'whymyrating', label: 'whymyrating', description: 'Web + Mobile Apps', icon: '📱', color: '#8b5cf6', tech: ['React', 'Next.js', 'React Native', 'TypeScript'], x: 50, y: 5, connections: ['whymyrating-engine', 'whymyrating-brand', 'whymyrating-templates'], }, { id: 'whymyrating-engine', label: 'whymyrating-engine', description: 'AI Pipelines & Backend API', icon: '⚙️', color: '#ef4444', tech: ['Python', 'FastAPI', 'PostgreSQL', 'Docker'], x: 10, y: 40, connections: ['nuc-server'], }, { id: 'whymyrating-brand', label: 'whymyrating-brand', description: 'Brand Assets & Guidelines', icon: '🎨', color: '#f59e0b', tech: ['SVG', 'Figma', 'CSS'], x: 50, y: 40, connections: [], }, { id: 'whymyrating-templates', label: 'whymyrating-templates', description: 'Email & Doc Templates', icon: '📄', color: '#10b981', tech: ['Next.js', 'React Email', 'TypeScript'], x: 90, y: 40, connections: [], }, { id: 'whyrating-hub', label: 'whyrating-hub', description: 'Internal Dashboard (You are here)', icon: '🏠', color: '#3b82f6', tech: ['Next.js', 'Tailwind', 'TypeScript'], x: 50, y: 75, connections: [], }, { id: 'nuc-server', label: 'NUC Server', description: 'Self-hosted Infrastructure', icon: '🖥️', color: '#6366f1', tech: ['Docker', 'PostgreSQL', 'Nginx', 'Gitea'], x: 10, y: 75, connections: ['whyrating-hub'], }, ]; const DiagramNode = ({ node, index, onHover, isHighlighted, }: { node: NodeConfig; index: number; onHover: (id: string | null) => void; isHighlighted: boolean; }) => { const [isHovered, setIsHovered] = useState(false); const [isVisible, setIsVisible] = useState(false); useEffect(() => { const timer = setTimeout(() => setIsVisible(true), index * 100); return () => clearTimeout(timer); }, [index]); return (
{ setIsHovered(true); onHover(node.id); }} onMouseLeave={() => { setIsHovered(false); onHover(null); }} >
{/* Pulse animation for hovered node */} {isHovered && (
)} {/* Icon */}
{node.icon}
{/* Label */}
{node.label}
{/* Description */}
{node.description}
{/* Tech stack - shown on hover */}
{node.tech.map((t, i) => ( {t} ))}
); }; const ConnectionLine = ({ from, to, isHighlighted, delay, }: { from: NodeConfig; to: NodeConfig; isHighlighted: boolean; delay: number; }) => { const [isVisible, setIsVisible] = useState(false); useEffect(() => { const timer = setTimeout(() => setIsVisible(true), delay); return () => clearTimeout(timer); }, [delay]); // Calculate line positions (center of nodes) const x1 = from.x; const y1 = from.y + 8; // Offset for node height const x2 = to.x; const y2 = to.y; // Calculate control points for curved line const midY = (y1 + y2) / 2; return ( {/* Animated dot along the path */} {isHighlighted && isVisible && ( )} ); }; export default function InfrastructureDiagram() { const [hoveredNode, setHoveredNode] = useState(null); const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); if (!mounted) { return (
Loading diagram...
); } // Get all connections const connections: { from: NodeConfig; to: NodeConfig }[] = []; nodes.forEach((node) => { node.connections.forEach((targetId) => { const target = nodes.find((n) => n.id === targetId); if (target) { connections.push({ from: node, to: target }); } }); }); // Determine which nodes/connections are highlighted const getHighlightedNodes = () => { if (!hoveredNode) return null; const node = nodes.find((n) => n.id === hoveredNode); if (!node) return null; return new Set([hoveredNode, ...node.connections]); }; const highlightedSet = getHighlightedNodes(); return (
{/* Background grid */}
{/* Diagram container */}
{/* Connection lines */} {connections.map(({ from, to }, i) => ( ))} {/* Nodes */} {nodes.map((node, i) => ( ))}
{/* Legend */}
{[ { color: '#8b5cf6', label: 'Main Apps' }, { color: '#ef4444', label: 'Backend/AI' }, { color: '#f59e0b', label: 'Brand' }, { color: '#10b981', label: 'Templates' }, { color: '#3b82f6', label: 'Dashboard' }, { color: '#6366f1', label: 'Infrastructure' }, ].map((item) => (
{item.label}
))}
{/* Global styles for animations */}
); }