/**
* 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
Select a project to view notifications
Loading notifications...
{error}
{unreadCount > 0 ? `${unreadCount} unread` : 'All caught up!'}
No notifications
Notifications will appear here when features are ready for review or operations complete.
{formatRelativeTime(new Date(notification.createdAt))}