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 */}
{/* 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) => (
))}
);
}