feat(notifications): implement notification system with CRUD operations and UI integration
All checks were successful
Build & Push Docker Image / build (push) Successful in 6m27s
All checks were successful
Build & Push Docker Image / build (push) Successful in 6m27s
This commit is contained in:
94
src/contexts/NotificationContext.jsx
Normal file
94
src/contexts/NotificationContext.jsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import { createContext, useContext, useState, useEffect, useRef, useCallback } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useAuth } from './AuthContext';
|
||||
import api from '../services/api';
|
||||
|
||||
const NotificationContext = createContext();
|
||||
|
||||
export function NotificationProvider({ children }) {
|
||||
const { user } = useAuth();
|
||||
const [notifications, setNotifications] = useState([]);
|
||||
const [unreadCount, setUnreadCount] = useState(0);
|
||||
// Track seen IDs to detect genuinely new arrivals and show toasts
|
||||
const seenIds = useRef(new Set());
|
||||
const initialized = useRef(false);
|
||||
|
||||
const fetch = useCallback(async () => {
|
||||
if (!user) return;
|
||||
try {
|
||||
const res = await api.get('/notifications');
|
||||
const incoming = res.data.notifications || [];
|
||||
setNotifications(incoming);
|
||||
setUnreadCount(res.data.unreadCount || 0);
|
||||
|
||||
// First fetch: just seed the seen-set without toasting
|
||||
if (!initialized.current) {
|
||||
incoming.forEach(n => seenIds.current.add(n.id));
|
||||
initialized.current = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Subsequent fetches: toast new unread notifications
|
||||
const newItems = incoming.filter(n => !n.read && !seenIds.current.has(n.id));
|
||||
newItems.forEach(n => {
|
||||
seenIds.current.add(n.id);
|
||||
const icon = notificationIcon(n.type);
|
||||
toast(`${icon} ${n.title}`, { duration: 5000 });
|
||||
});
|
||||
} catch {
|
||||
/* silent – server may not be reachable */
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) {
|
||||
setNotifications([]);
|
||||
setUnreadCount(0);
|
||||
seenIds.current = new Set();
|
||||
initialized.current = false;
|
||||
return;
|
||||
}
|
||||
fetch();
|
||||
const interval = setInterval(fetch, 30_000);
|
||||
return () => clearInterval(interval);
|
||||
}, [user, fetch]);
|
||||
|
||||
const markRead = async (id) => {
|
||||
try {
|
||||
await api.post(`/notifications/${id}/read`);
|
||||
setNotifications(prev =>
|
||||
prev.map(n => (n.id === id ? { ...n, read: 1 } : n)),
|
||||
);
|
||||
setUnreadCount(prev => Math.max(0, prev - 1));
|
||||
} catch { /* silent */ }
|
||||
};
|
||||
|
||||
const markAllRead = async () => {
|
||||
try {
|
||||
await api.post('/notifications/read-all');
|
||||
setNotifications(prev => prev.map(n => ({ ...n, read: 1 })));
|
||||
setUnreadCount(0);
|
||||
} catch { /* silent */ }
|
||||
};
|
||||
|
||||
return (
|
||||
<NotificationContext.Provider value={{ notifications, unreadCount, markRead, markAllRead, refresh: fetch }}>
|
||||
{children}
|
||||
</NotificationContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useNotifications() {
|
||||
const ctx = useContext(NotificationContext);
|
||||
if (!ctx) throw new Error('useNotifications must be used within NotificationProvider');
|
||||
return ctx;
|
||||
}
|
||||
|
||||
function notificationIcon(type) {
|
||||
switch (type) {
|
||||
case 'room_share_added': return '🔗';
|
||||
case 'room_share_removed': return '🚫';
|
||||
case 'federation_invite_received': return '📩';
|
||||
default: return '🔔';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user