import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { motion } from 'framer-motion'; import { Switch } from '~/components/ui/Switch'; import { logStore, type LogEntry } from '~/lib/stores/logs'; import { useStore } from '@nanostores/react'; import { classNames } from '~/utils/classNames'; interface SelectOption { value: string; label: string; icon?: string; color?: string; } const logLevelOptions: SelectOption[] = [ { value: 'all', label: 'All Levels', icon: 'i-ph:funnel', color: '#9333ea', }, { value: 'info', label: 'Info', icon: 'i-ph:info', color: '#3b82f6', }, { value: 'warning', label: 'Warning', icon: 'i-ph:warning', color: '#f59e0b', }, { value: 'error', label: 'Error', icon: 'i-ph:x-circle', color: '#ef4444', }, { value: 'debug', label: 'Debug', icon: 'i-ph:bug', color: '#6b7280', }, ]; const logCategoryOptions: SelectOption[] = [ { value: 'all', label: 'All Categories', icon: 'i-ph:squares-four', color: '#9333ea', }, { value: 'api', label: 'API', icon: 'i-ph:cloud', color: '#3b82f6', }, { value: 'auth', label: 'Auth', icon: 'i-ph:key', color: '#f59e0b', }, { value: 'database', label: 'Database', icon: 'i-ph:database', color: '#10b981', }, { value: 'network', label: 'Network', icon: 'i-ph:wifi-high', color: '#6366f1', }, { value: 'performance', label: 'Performance', icon: 'i-ph:chart-line-up', color: '#8b5cf6', }, ]; interface LogEntryItemProps { log: LogEntry; isExpanded: boolean; use24Hour: boolean; showTimestamp: boolean; } const LogEntryItem = ({ log, isExpanded: forceExpanded, use24Hour, showTimestamp }: LogEntryItemProps) => { const [localExpanded, setLocalExpanded] = useState(forceExpanded); // Update local expanded state when forceExpanded changes useEffect(() => { setLocalExpanded(forceExpanded); }, [forceExpanded]); const timestamp = useMemo(() => { const date = new Date(log.timestamp); if (use24Hour) { return date.toLocaleTimeString('en-US', { hour12: false }); } return date.toLocaleTimeString('en-US', { hour12: true }); }, [log.timestamp, use24Hour]); const levelColor = useMemo(() => { switch (log.level) { case 'error': return 'text-red-500 bg-red-50 dark:bg-red-500/10'; case 'warning': return 'text-yellow-500 bg-yellow-50 dark:bg-yellow-500/10'; case 'debug': return 'text-gray-500 bg-gray-50 dark:bg-gray-500/10'; default: return 'text-blue-500 bg-blue-50 dark:bg-blue-500/10'; } }, [log.level]); return (
{log.level}
{showTimestamp &&
{timestamp}
}
{log.message}
{log.details && ( )}
{log.category && (
{log.category}
)}
{localExpanded && log.details && (
            {JSON.stringify(log.details, null, 2)}
          
)}
); }; export function EventLogsTab() { const logs = useStore(logStore.logs); const [selectedLevel, setSelectedLevel] = useState('all'); const [selectedCategory, setSelectedCategory] = useState('all'); const [searchQuery, setSearchQuery] = useState(''); const [use24Hour, setUse24Hour] = useState(false); const [autoExpand, setAutoExpand] = useState(false); const [showTimestamps, setShowTimestamps] = useState(true); const [showLevelFilter, setShowLevelFilter] = useState(false); const [showCategoryFilter, setShowCategoryFilter] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); const logsContainerRef = useRef(null); const levelFilterRef = useRef(null); const categoryFilterRef = useRef(null); const filteredLogs = useMemo(() => { return logStore.getFilteredLogs( selectedLevel === 'all' ? undefined : (selectedLevel as LogEntry['level']), selectedCategory === 'all' ? undefined : (selectedCategory as LogEntry['category']), searchQuery, ); }, [logs, selectedLevel, selectedCategory, searchQuery]); const handleExportLogs = useCallback(() => { const exportData = { timestamp: new Date().toISOString(), logs: filteredLogs, filters: { level: selectedLevel, category: selectedCategory, searchQuery, }, }; const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `bolt-logs-${new Date().toISOString()}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }, [filteredLogs, selectedLevel, selectedCategory, searchQuery]); const handleRefresh = useCallback(async () => { setIsRefreshing(true); await logStore.refreshLogs(); setTimeout(() => setIsRefreshing(false), 500); // Keep animation visible for at least 500ms }, []); // Close filters when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (levelFilterRef.current && !levelFilterRef.current.contains(event.target as Node)) { setShowLevelFilter(false); } if (categoryFilterRef.current && !categoryFilterRef.current.contains(event.target as Node)) { setShowCategoryFilter(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, []); const selectedLevelOption = logLevelOptions.find((opt) => opt.value === selectedLevel); const selectedCategoryOption = logCategoryOptions.find((opt) => opt.value === selectedCategory); return (
{/* Header */}

Event Logs

Track system events and debug information

{/* Top Controls */}
{/* Search */}
setSearchQuery(e.target.value)} className="w-full px-3 py-1.5 pl-9 rounded-lg text-sm bg-white/50 dark:bg-gray-800/30 border border-gray-200/50 dark:border-gray-700/50" />
{/* Right Controls */}
Show Timestamps
24h Time
Auto Expand
Export Logs
{/* Filters */}
{/* Level Filter */}
{showLevelFilter && (
{logLevelOptions.map((option) => ( ))}
)}
{/* Category Filter */}
{showCategoryFilter && (
{logCategoryOptions.map((option) => ( ))}
)}
{/* Logs Container */}
{filteredLogs.map((log) => ( ))}
); }