/** * Notifications View - Full page view for all notifications */ import { useEffect, useCallback } from 'react'; import { useAppStore } from '@/store/app-store'; import { useNotificationsStore } from '@/store/notifications-store'; import { useLoadNotifications, useNotificationEvents } from '@/hooks/use-notification-events'; import { getHttpApiClient } from '@/lib/http-api-client'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Bell, Check, CheckCheck, Trash2, ExternalLink } from 'lucide-react'; import { Spinner } from '@/components/ui/spinner'; import { useNavigate } from '@tanstack/react-router'; import type { Notification } from '@automaker/types'; /** * Format a date as relative time (e.g., "2 minutes ago", "3 hours ago") */ function formatRelativeTime(date: Date): string { const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffSec = Math.floor(diffMs / 1000); const diffMin = Math.floor(diffSec / 60); const diffHour = Math.floor(diffMin / 60); const diffDay = Math.floor(diffHour / 24); if (diffSec < 60) return 'just now'; if (diffMin < 60) return `${diffMin} minute${diffMin === 1 ? '' : 's'} ago`; if (diffHour < 24) return `${diffHour} hour${diffHour === 1 ? '' : 's'} ago`; if (diffDay < 7) return `${diffDay} day${diffDay === 1 ? '' : 's'} ago`; return date.toLocaleDateString(); } export function NotificationsView() { const { currentProject } = useAppStore(); const projectPath = currentProject?.path ?? null; const navigate = useNavigate(); const { notifications, unreadCount, isLoading, error, setNotifications, setUnreadCount, markAsRead, dismissNotification, markAllAsRead, dismissAll, } = useNotificationsStore(); // Load notifications when project changes useLoadNotifications(projectPath); // Subscribe to real-time notification events useNotificationEvents(projectPath); const handleMarkAsRead = useCallback( async (notificationId: string) => { if (!projectPath) return; // Optimistic update markAsRead(notificationId); // Sync with server const api = getHttpApiClient(); await api.notifications.markAsRead(projectPath, notificationId); }, [projectPath, markAsRead] ); const handleDismiss = useCallback( async (notificationId: string) => { if (!projectPath) return; // Optimistic update dismissNotification(notificationId); // Sync with server const api = getHttpApiClient(); await api.notifications.dismiss(projectPath, notificationId); }, [projectPath, dismissNotification] ); const handleMarkAllAsRead = useCallback(async () => { if (!projectPath) return; // Optimistic update markAllAsRead(); // Sync with server const api = getHttpApiClient(); await api.notifications.markAsRead(projectPath); }, [projectPath, markAllAsRead]); const handleDismissAll = useCallback(async () => { if (!projectPath) return; // Optimistic update dismissAll(); // Sync with server const api = getHttpApiClient(); await api.notifications.dismiss(projectPath); }, [projectPath, dismissAll]); const handleNotificationClick = useCallback( (notification: Notification) => { // Mark as read handleMarkAsRead(notification.id); // Navigate to the relevant view based on notification type if (notification.featureId) { // Navigate to board view - feature will be selected navigate({ to: '/board' }); } }, [handleMarkAsRead, navigate] ); const getNotificationIcon = (type: string) => { switch (type) { case 'feature_waiting_approval': return ; case 'feature_verified': return ; case 'spec_regeneration_complete': return ; case 'agent_complete': return ; default: return ; } }; if (!projectPath) { return (

Select a project to view notifications

); } if (isLoading) { return (

Loading notifications...

); } if (error) { return (

{error}

); } return (

Notifications

{unreadCount > 0 ? `${unreadCount} unread` : 'All caught up!'}

{notifications.length > 0 && (
)}
{notifications.length === 0 ? (

No notifications

Notifications will appear here when features are ready for review or operations complete.

) : (
{notifications.map((notification) => ( handleNotificationClick(notification)} >
{getNotificationIcon(notification.type)}
{notification.title} {!notification.read && ( )}
{notification.message}

{formatRelativeTime(new Date(notification.createdAt))}

{!notification.read && ( )} {notification.featureId && ( )}
))}
)}
); }