/** * Live Feed Component * * Displays a real-time feed of events from the LocalGreenChain system. */ import React, { useMemo } from 'react'; import classNames from 'classnames'; import { useLiveFeed } from '../../lib/realtime/useSocket'; import type { LiveFeedItem, RoomType, TransparencyEventType } from '../../lib/realtime/types'; import { EventCategory, getEventCategory } from '../../lib/realtime/events'; import { ConnectionStatus } from './ConnectionStatus'; interface LiveFeedProps { rooms?: RoomType[]; eventTypes?: TransparencyEventType[]; maxItems?: number; showConnectionStatus?: boolean; showTimestamps?: boolean; showClearButton?: boolean; filterCategory?: EventCategory; className?: string; emptyMessage?: string; } /** * Format timestamp for display */ function formatTimestamp(timestamp: number): string { const date = new Date(timestamp); const now = new Date(); const diffMs = now.getTime() - timestamp; const diffSec = Math.floor(diffMs / 1000); const diffMin = Math.floor(diffSec / 60); const diffHour = Math.floor(diffMin / 60); if (diffSec < 60) { return 'Just now'; } else if (diffMin < 60) { return `${diffMin}m ago`; } else if (diffHour < 24) { return `${diffHour}h ago`; } else { return date.toLocaleDateString(); } } /** * Get color classes for event type */ function getColorClasses(color: string): { bg: string; border: string; text: string } { switch (color) { case 'green': return { bg: 'bg-green-50', border: 'border-green-200', text: 'text-green-800', }; case 'blue': return { bg: 'bg-blue-50', border: 'border-blue-200', text: 'text-blue-800', }; case 'yellow': return { bg: 'bg-yellow-50', border: 'border-yellow-200', text: 'text-yellow-800', }; case 'red': return { bg: 'bg-red-50', border: 'border-red-200', text: 'text-red-800', }; case 'purple': return { bg: 'bg-purple-50', border: 'border-purple-200', text: 'text-purple-800', }; case 'gray': default: return { bg: 'bg-gray-50', border: 'border-gray-200', text: 'text-gray-800', }; } } /** * Single feed item component */ function FeedItem({ item, showTimestamp, }: { item: LiveFeedItem; showTimestamp: boolean; }) { const colors = getColorClasses(item.formatted.color); return (
{/* Icon */} {item.formatted.icon} {/* Content */}
{item.formatted.title} {showTimestamp && ( {formatTimestamp(item.timestamp)} )}

{item.formatted.description}

); } /** * Live Feed component */ export function LiveFeed({ rooms, eventTypes, maxItems = 20, showConnectionStatus = true, showTimestamps = true, showClearButton = true, filterCategory, className, emptyMessage = 'No events yet. Real-time updates will appear here.', }: LiveFeedProps) { const { items, isConnected, status, clearFeed } = useLiveFeed({ rooms, eventTypes, maxEvents: maxItems, }); // Filter items by category if specified const filteredItems = useMemo(() => { if (!filterCategory) return items; return items.filter((item) => { const category = getEventCategory(item.event.type); return category === filterCategory; }); }, [items, filterCategory]); return (
{/* Header */}

Live Feed

{showConnectionStatus && }
{filteredItems.length > 0 && ( {filteredItems.length} event{filteredItems.length !== 1 ? 's' : ''} )} {showClearButton && filteredItems.length > 0 && ( )}
{/* Feed content */}
{filteredItems.length === 0 ? (
📡

{emptyMessage}

{!isConnected && (

Status: {status}

)}
) : ( filteredItems.map((item) => ( )) )}
); } /** * Compact live feed for sidebars */ export function CompactLiveFeed({ maxItems = 5, className, }: { maxItems?: number; className?: string; }) { const { items } = useLiveFeed({ maxEvents: maxItems }); if (items.length === 0) { return null; } return (
{items.slice(0, maxItems).map((item) => (
{item.formatted.icon} {item.formatted.description}
))}
); } export default LiveFeed;