From 5e204c278b8d7d59fbaf3ddb33252b1ec3479113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Guti=C3=A9rrez?= <35082514+alezmad@users.noreply.github.com> Date: Mon, 9 Feb 2026 01:52:45 +0000 Subject: [PATCH] Add InfrastructureDiagram component missing from previous commit Co-Authored-By: Claude Opus 4.6 --- src/components/InfrastructureDiagram.tsx | 353 +++++++++++++++++++++++ src/components/index.ts | 1 + 2 files changed, 354 insertions(+) create mode 100644 src/components/InfrastructureDiagram.tsx diff --git a/src/components/InfrastructureDiagram.tsx b/src/components/InfrastructureDiagram.tsx new file mode 100644 index 0000000..832b680 --- /dev/null +++ b/src/components/InfrastructureDiagram.tsx @@ -0,0 +1,353 @@ +'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 */} + +
+ ); +} diff --git a/src/components/index.ts b/src/components/index.ts index 1c618ce..6b6cae1 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,2 +1,3 @@ export { Icon, icons } from './Icons'; export { WhyMyRatingLogo } from './WhyMyRatingLogo'; +export { default as InfrastructureDiagram } from './InfrastructureDiagram';