diff --git a/src/client/src/components/GameLogPanel.tsx b/src/client/src/components/GameLogPanel.tsx new file mode 100644 index 0000000..5e001c5 --- /dev/null +++ b/src/client/src/components/GameLogPanel.tsx @@ -0,0 +1,87 @@ +import React, { useEffect, useRef } from 'react'; +import { useGameLog, GameLogEntry } from '../contexts/GameLogContext'; +import { ScrollText, User, Bot, Info, AlertTriangle, ShieldAlert } from 'lucide-react'; + +interface GameLogPanelProps { + className?: string; + maxHeight?: string; +} + +export const GameLogPanel: React.FC = ({ className, maxHeight = '200px' }) => { + const { logs } = useGameLog(); + const bottomRef = useRef(null); + + // Auto-scroll to bottom on new logs + useEffect(() => { + bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [logs]); + + const getIcon = (type: GameLogEntry['type'], source: string) => { + if (source === 'System') return ; + if (type === 'error') return ; + if (type === 'combat') return ; + if (source.includes('Bot')) return ; + return ; + }; + + const getTypeStyle = (type: GameLogEntry['type']) => { + switch (type) { + case 'error': return 'text-red-400 bg-red-900/10 border-red-900/30'; + case 'warning': return 'text-amber-400 bg-amber-900/10 border-amber-900/30'; + case 'success': return 'text-emerald-400 bg-emerald-900/10 border-emerald-900/30'; + case 'combat': return 'text-red-300 bg-red-900/20 border-red-900/40 font-bold'; + case 'action': return 'text-blue-300 bg-blue-900/10 border-blue-900/30'; + default: return 'text-slate-300 border-transparent'; + } + }; + + return ( +
+
+ + Game Log +
+ +
+ {logs.length === 0 && ( +
+ Game started. Actions will appear here. +
+ )} + + {logs.map((log) => ( +
+
+
+ {getIcon(log.type, log.source)} +
+
+ {/* Source Header */} + {log.source !== 'System' && ( + + {log.source} + + )} + {/* Message Body */} + + {log.message} + +
+ + {new Date(log.timestamp).toLocaleTimeString([], { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' })} + +
+
+ ))} +
+
+
+ ); +}; diff --git a/src/client/src/components/SidePanelPreview.tsx b/src/client/src/components/SidePanelPreview.tsx index f6a7d7d..ff4ec5a 100644 --- a/src/client/src/components/SidePanelPreview.tsx +++ b/src/client/src/components/SidePanelPreview.tsx @@ -1,8 +1,9 @@ -import React from 'react'; +import React, { forwardRef } from 'react'; import { CardVisual, VisualCard } from './CardVisual'; import { Eye, ChevronLeft } from 'lucide-react'; import { ManaIcon } from './ManaIcon'; import { formatOracleText } from '../utils/textUtils'; +import { GameLogPanel } from './GameLogPanel'; interface SidePanelPreviewProps { card: VisualCard | null; @@ -10,23 +11,25 @@ interface SidePanelPreviewProps { isCollapsed: boolean; onToggleCollapse: (collapsed: boolean) => void; onResizeStart?: (e: React.MouseEvent | React.TouchEvent) => void; - className?: string; // For additional styling (positioning, z-index, etc) + className?: string; children?: React.ReactNode; + showLog?: boolean; } -export const SidePanelPreview: React.FC = ({ +export const SidePanelPreview = forwardRef(({ card, width, isCollapsed, onToggleCollapse, onResizeStart, className, - children -}) => { + children, + showLog = true, +}, ref) => { // If collapsed, render the collapsed strip if (isCollapsed) { return ( -
+