refactor Notifications
This commit is contained in:
parent
dcb3e1fe9a
commit
05ec62595d
56
app/api/notifications/mark-all-read/route.ts
Normal file
56
app/api/notifications/mark-all-read/route.ts
Normal 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 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -30,19 +30,28 @@ type FilterOption = 'all' | 'email' | 'rocketchat' | 'leantime' | 'calendar';
|
|||||||
// Use React.memo to prevent unnecessary re-renders
|
// Use React.memo to prevent unnecessary re-renders
|
||||||
export const NotificationBadge = memo(function NotificationBadge({ className }: NotificationBadgeProps) {
|
export const NotificationBadge = memo(function NotificationBadge({ className }: NotificationBadgeProps) {
|
||||||
const { data: session, status } = useSession();
|
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 hasUnread = notificationCount.unread > 0;
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [sortBy, setSortBy] = useState<SortOption>('newest');
|
const [sortBy, setSortBy] = useState<SortOption>('newest');
|
||||||
const [filterBy, setFilterBy] = useState<FilterOption>('all');
|
const [filterBy, setFilterBy] = useState<FilterOption>('all');
|
||||||
const [displayLimit, setDisplayLimit] = useState(10);
|
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(() => {
|
useEffect(() => {
|
||||||
if (isOpen && status === 'authenticated') {
|
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
|
// Sort and filter notifications
|
||||||
const sortedAndFilteredNotifications = useMemo(() => {
|
const sortedAndFilteredNotifications = useMemo(() => {
|
||||||
|
|||||||
@ -69,6 +69,40 @@ export function useNotifications() {
|
|||||||
}
|
}
|
||||||
}, [session?.user]);
|
}, [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
|
// Mark notification as read
|
||||||
const markAsRead = useCallback(async (notificationId: string) => {
|
const markAsRead = useCallback(async (notificationId: string) => {
|
||||||
if (!session?.user || !isMountedRef.current) return false;
|
if (!session?.user || !isMountedRef.current) return false;
|
||||||
@ -208,5 +242,6 @@ export function useNotifications() {
|
|||||||
fetchNotifications,
|
fetchNotifications,
|
||||||
fetchNotificationCount: () => fetchNotificationCount(true),
|
fetchNotificationCount: () => fetchNotificationCount(true),
|
||||||
markAsRead,
|
markAsRead,
|
||||||
|
markAllAsRead,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user