- Introduced useRef hooks to store references for fetchNotifications, authenticated, and connected states, improving performance and reducing unnecessary re-renders. - Updated useEffect hooks to utilize the refs for fetching notifications, enhancing clarity and responsiveness to state changes while maintaining functionality.
210 lines
6.1 KiB
JavaScript
210 lines
6.1 KiB
JavaScript
import {
|
|
createContext,
|
|
useState,
|
|
useContext,
|
|
useCallback,
|
|
useEffect,
|
|
useRef
|
|
} from 'react'
|
|
import { useLocation } from 'react-router-dom'
|
|
import { notification, Drawer } from 'antd'
|
|
import PropTypes from 'prop-types'
|
|
import { AuthContext } from './AuthContext'
|
|
import { ApiServerContext } from './ApiServerContext'
|
|
import NotificationCenter from '../common/NotificationCenter'
|
|
import Notification from '../common/Notification'
|
|
import { useMediaQuery } from 'react-responsive'
|
|
|
|
const NotificationContext = createContext()
|
|
|
|
const NotificationProvider = ({ children }) => {
|
|
const [api, contextHolder] = notification.useNotification()
|
|
const location = useLocation()
|
|
const { authenticated } = useContext(AuthContext)
|
|
const {
|
|
showError,
|
|
fetchNotificationsApi,
|
|
markNotificationAsReadApi,
|
|
markAllNotificationsAsReadApi,
|
|
deleteNotificationApi,
|
|
deleteAllNotificationsApi,
|
|
registerNotificationListener,
|
|
connected
|
|
} = useContext(ApiServerContext)
|
|
|
|
const [notificationCenterVisible, setNotificationCenterVisible] =
|
|
useState(false)
|
|
const [notifications, setNotifications] = useState([])
|
|
const [notificationsLoading, setNotificationsLoading] = useState(false)
|
|
|
|
const isMobile = useMediaQuery({ maxWidth: 768 })
|
|
|
|
const fetchNotifications = useCallback(async () => {
|
|
if (!authenticated) return []
|
|
setNotificationsLoading(true)
|
|
try {
|
|
const data = await fetchNotificationsApi()
|
|
setNotifications(data)
|
|
return data
|
|
} catch (err) {
|
|
console.error(err)
|
|
const is404 = err?.response?.status === 404
|
|
if (is404) {
|
|
setNotifications([])
|
|
} else {
|
|
showError(err, () => fetchNotifications())
|
|
}
|
|
return []
|
|
} finally {
|
|
setNotificationsLoading(false)
|
|
}
|
|
}, [authenticated, fetchNotificationsApi, showError])
|
|
|
|
const fetchNotificationsRef = useRef(fetchNotifications)
|
|
const authenticatedRef = useRef(authenticated)
|
|
const connectedRef = useRef(connected)
|
|
fetchNotificationsRef.current = fetchNotifications
|
|
authenticatedRef.current = authenticated
|
|
connectedRef.current = connected
|
|
|
|
const markNotificationAsRead = useCallback(
|
|
async (notificationId) => {
|
|
try {
|
|
await markNotificationAsReadApi(notificationId)
|
|
setNotifications((prev) =>
|
|
prev.map((n) => (n._id === notificationId ? { ...n, read: true } : n))
|
|
)
|
|
} catch (err) {
|
|
console.error(err)
|
|
showError(err, () => markNotificationAsRead(notificationId))
|
|
}
|
|
},
|
|
[markNotificationAsReadApi, showError]
|
|
)
|
|
|
|
const markAllNotificationsAsRead = useCallback(async () => {
|
|
try {
|
|
await markAllNotificationsAsReadApi()
|
|
setNotifications((prev) => prev.map((n) => ({ ...n, read: true })))
|
|
} catch (err) {
|
|
console.error(err)
|
|
showError(err, () => markAllNotificationsAsRead())
|
|
}
|
|
}, [markAllNotificationsAsReadApi, showError])
|
|
|
|
const deleteNotification = useCallback(
|
|
async (notificationId) => {
|
|
try {
|
|
await deleteNotificationApi(notificationId)
|
|
setNotifications((prev) => prev.filter((n) => n._id !== notificationId))
|
|
} catch (err) {
|
|
console.error(err)
|
|
showError(err, () => deleteNotification(notificationId))
|
|
}
|
|
},
|
|
[deleteNotificationApi, showError]
|
|
)
|
|
|
|
const deleteAllNotifications = useCallback(async () => {
|
|
try {
|
|
await deleteAllNotificationsApi()
|
|
setNotifications([])
|
|
} catch (err) {
|
|
console.error(err)
|
|
showError(err, () => deleteAllNotifications())
|
|
}
|
|
}, [deleteAllNotificationsApi, showError])
|
|
|
|
const toggleNotificationCenter = useCallback(() => {
|
|
setNotificationCenterVisible((prev) => !prev)
|
|
}, [])
|
|
|
|
const unreadCount = notifications.filter((n) => !n.read).length
|
|
|
|
// Initial load / when we become authenticated and connected
|
|
useEffect(() => {
|
|
if (!authenticated || !connected) return
|
|
fetchNotificationsRef.current()
|
|
}, [authenticated, connected])
|
|
|
|
// Refresh when the notification center opens
|
|
useEffect(() => {
|
|
if (!notificationCenterVisible) return
|
|
if (!authenticatedRef.current || !connectedRef.current) return
|
|
fetchNotificationsRef.current()
|
|
}, [notificationCenterVisible])
|
|
|
|
useEffect(() => {
|
|
setNotificationCenterVisible(false)
|
|
}, [location.pathname])
|
|
|
|
useEffect(() => {
|
|
if (!authenticated || !registerNotificationListener) return
|
|
const handleNotification = (notif) => {
|
|
setNotifications((prev) => {
|
|
const exists = prev.some((n) => n._id === notif._id)
|
|
if (exists) return prev
|
|
return [{ ...notif, read: false }, ...prev]
|
|
})
|
|
const show = api.open
|
|
show({
|
|
message: (
|
|
<Notification
|
|
notification={notif}
|
|
onDelete={deleteNotification}
|
|
showCard={false}
|
|
showDelete={false}
|
|
showExtraInfo={false}
|
|
inlineIcon={true}
|
|
showDetails={false}
|
|
largeTitleSpacing={true}
|
|
/>
|
|
),
|
|
icon: null,
|
|
duration: 5,
|
|
key: notif._id
|
|
})
|
|
}
|
|
const unregister = registerNotificationListener(handleNotification)
|
|
return unregister
|
|
}, [authenticated, registerNotificationListener, api, deleteNotification])
|
|
|
|
return (
|
|
<NotificationContext.Provider
|
|
value={{
|
|
notificationCenterVisible,
|
|
toggleNotificationCenter,
|
|
notifications,
|
|
fetchNotifications,
|
|
markNotificationAsRead,
|
|
markAllNotificationsAsRead,
|
|
deleteNotification,
|
|
deleteAllNotifications,
|
|
unreadCount,
|
|
notificationsLoading
|
|
}}
|
|
>
|
|
{contextHolder}
|
|
{children}
|
|
<Drawer
|
|
title='Notifications'
|
|
placement='right'
|
|
width={isMobile ? '100%' : 460}
|
|
onClose={() => setNotificationCenterVisible(false)}
|
|
open={notificationCenterVisible}
|
|
>
|
|
<NotificationCenter
|
|
visible={notificationCenterVisible}
|
|
onClose={() => setNotificationCenterVisible(false)}
|
|
/>
|
|
</Drawer>
|
|
</NotificationContext.Provider>
|
|
)
|
|
}
|
|
|
|
NotificationProvider.propTypes = {
|
|
children: PropTypes.node.isRequired
|
|
}
|
|
|
|
export { NotificationContext, NotificationProvider }
|