refactor Notifications

This commit is contained in:
alma 2026-01-16 00:27:07 +01:00
parent dcb3e1fe9a
commit 05ec62595d
3 changed files with 103 additions and 3 deletions

View File

@ -0,0 +1,56 @@
import { NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from "@/app/api/auth/options";
import { NotificationRegistry } from '@/lib/services/notifications/notification-registry';
import { logger } from '@/lib/logger';
// POST /api/notifications/mark-all-read
export async function POST(request: Request) {
try {
const session = await getServerSession(authOptions);
if (!session?.user?.id) {
return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
}
const registry = NotificationRegistry.getInstance();
// Reset all counts to 0 for this user
try {
const redis = await import('@/lib/redis').then(m => m.getRedisClient());
const countKey = `notifications:count:${session.user.id}`;
// Set all counts to 0
const emptyCount = {
total: 0,
unread: 0,
sources: {
email: { total: 0, unread: 0 },
rocketchat: { total: 0, unread: 0 },
leantime: { total: 0, unread: 0 },
calendar: { total: 0, unread: 0 },
},
};
await redis.set(countKey, JSON.stringify(emptyCount), 'EX', 30);
logger.debug('[NOTIFICATIONS_MARK_ALL_READ] All notifications marked as read', {
userId: session.user.id,
});
} catch (error) {
logger.error('[NOTIFICATIONS_MARK_ALL_READ] Error marking all as read', {
userId: session.user.id,
error: error instanceof Error ? error.message : String(error),
});
}
return NextResponse.json({ success: true });
} catch (error: any) {
logger.error('[NOTIFICATIONS_MARK_ALL_READ] Error', {
error: error instanceof Error ? error.message : String(error),
});
return NextResponse.json(
{ error: "Internal server error", message: error.message },
{ status: 500 }
);
}
}

View File

@ -30,19 +30,28 @@ type FilterOption = 'all' | 'email' | 'rocketchat' | 'leantime' | 'calendar';
// Use React.memo to prevent unnecessary re-renders
export const NotificationBadge = memo(function NotificationBadge({ className }: NotificationBadgeProps) {
const { data: session, status } = useSession();
const { notifications, notificationCount, fetchNotifications, markAsRead, loading, error } = useNotifications();
const { notifications, notificationCount, fetchNotifications, markAsRead, markAllAsRead, loading, error } = useNotifications();
const hasUnread = notificationCount.unread > 0;
const [isOpen, setIsOpen] = useState(false);
const [sortBy, setSortBy] = useState<SortOption>('newest');
const [filterBy, setFilterBy] = useState<FilterOption>('all');
const [displayLimit, setDisplayLimit] = useState(10);
const [hasMarkedAsRead, setHasMarkedAsRead] = useState(false);
// Fetch notifications when dropdown opens
// When dropdown opens, mark all as read and fetch notifications
useEffect(() => {
if (isOpen && status === 'authenticated') {
fetchNotifications(1, 50, filterBy === 'all' ? undefined : filterBy);
// Mark all as read when opening (only once per open)
if (!hasMarkedAsRead) {
markAllAsRead();
setHasMarkedAsRead(true);
}
}, [isOpen, status, filterBy, fetchNotifications]);
fetchNotifications(1, 50, filterBy === 'all' ? undefined : filterBy);
} else if (!isOpen) {
// Reset flag when dropdown closes
setHasMarkedAsRead(false);
}
}, [isOpen, status, filterBy, fetchNotifications, markAllAsRead, hasMarkedAsRead]);
// Sort and filter notifications
const sortedAndFilteredNotifications = useMemo(() => {

View File

@ -69,6 +69,40 @@ export function useNotifications() {
}
}, [session?.user]);
// Mark all notifications as read (when dropdown is opened)
const markAllAsRead = useCallback(async () => {
if (!session?.user || !isMountedRef.current) return false;
try {
const response = await fetch('/api/notifications/mark-all-read', {
method: 'POST',
credentials: 'include',
});
if (!response.ok) {
throw new Error('Failed to mark all notifications as read');
}
// Update local state - reset count to 0
setNotificationCount({
total: 0,
unread: 0,
sources: {
email: { total: 0, unread: 0 },
rocketchat: { total: 0, unread: 0 },
leantime: { total: 0, unread: 0 },
calendar: { total: 0, unread: 0 },
},
});
return true;
} catch (err: any) {
console.error('Error marking all notifications as read:', err);
setError(err.message || 'Failed to mark all notifications as read');
return false;
}
}, [session?.user]);
// Mark notification as read
const markAsRead = useCallback(async (notificationId: string) => {
if (!session?.user || !isMountedRef.current) return false;
@ -208,5 +242,6 @@ export function useNotifications() {
fetchNotifications,
fetchNotificationCount: () => fetchNotificationCount(true),
markAsRead,
markAllAsRead,
};
}