import Avatar from '@/components/ui/Avatar'
import Badge from '@/components/ui/Badge'
import Button from '@/components/ui/Button'
import Dropdown from '@/components/ui/Dropdown'
import ScrollBar from '@/components/ui/ScrollBar'
import Spinner from '@/components/ui/Spinner'
import Tooltip from '@/components/ui/Tooltip'
import { AVATAR_URL } from '@/constants/app.constant'
import NotificationChannels from '@/constants/notification-channel.enum'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { getList, updateRead, updateReadAll, updateSent } from '@/services/notification.service'
import { useStoreState } from '@/store'
import withHeaderItem from '@/utils/hoc/withHeaderItem'
import { useLocalization } from '@/utils/hooks/useLocalization'
import useResponsive from '@/utils/hooks/useResponsive'
import useThemeClass from '@/utils/hooks/useThemeClass'
import isLastChild from '@/utils/isLastChild'
import classNames from 'classnames'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { useCallback, useEffect, useRef, useState } from 'react'
import { FaEnvelopeOpen } from 'react-icons/fa'
import { Link } from 'react-router-dom'
import { Notification as Notify, toast } from '../ui'
import { FcAdvertising } from 'react-icons/fc'
dayjs.extend(relativeTime)
type NotificationList = {
id: string
creatorId: string
tenantId?: string
notificationType: string
message: string
date: string
readed: boolean
}
const notificationHeight = 'h-72'
const notificationInterval = 120000 // 2 minutes
const notificationTypeAvatar = (creatorId: string, tenantId?: string) => {
return
}
const NotificationToggle = ({
className,
unreadCount,
}: {
className?: string
unreadCount: number
}) => {
return (
{unreadCount > 0 ? (
) : (
)}
)
}
const _Notification = ({ className }: { className?: string }) => {
const { translate } = useLocalization()
const [notificationList, setNotificationList] = useState([])
const [unreadNotificationCount, setUnreadNotificationCount] = useState(0)
const [noResult, setNoResult] = useState(false)
const [loading, setLoading] = useState(false)
const toastNotificationList = useRef([])
const desktopNotificationList = useRef([])
const pushNotificationInterval = useRef>()
const { bgTheme } = useThemeClass()
const { larger } = useResponsive()
const direction = useStoreState((state) => state.theme.direction)
const tabHasFocus = useStoreState((a) => a.base.common.tabHasFocus)
const tabHasFocusRef = useRef(tabHasFocus)
useEffect(() => {
tabHasFocusRef.current = tabHasFocus
}, [tabHasFocus])
const getReactNotificationCount = useCallback(async () => {
const resp = await getList({
channels: [NotificationChannels.UiActivity],
isListRequest: false,
isRead: false,
maxResultCount: 1,
})
setUnreadNotificationCount(resp.data?.totalCount ?? 0)
}, [setUnreadNotificationCount])
useEffect(() => {
getReactNotificationCount()
var intervalId = setInterval(() => {
if (tabHasFocusRef.current) {
getReactNotificationCount()
}
}, notificationInterval)
return () => {
clearInterval(intervalId)
}
}, [getReactNotificationCount])
const onNotificationOpen = useCallback(async () => {
const currentUnread = notificationList.filter((a) => !a.readed).length
if (currentUnread !== unreadNotificationCount) {
setLoading(true)
const resp = await getList({
channels: [NotificationChannels.UiActivity],
isListRequest: false,
maxResultCount: 1000,
})
const items = resp.data.items ?? []
for (const notification of items) {
await updateSent(notification.id, true)
}
const newNotificationList = items.map(
(a) =>
({
id: a.id,
notificationType: a.notificationType,
date: a.creationTime.toLocaleString(),
message: a.message,
creatorId: a.creatorId,
tenantId: a.tenantId,
readed: a.isRead,
}) as NotificationList,
)
setLoading(false)
setNotificationList(newNotificationList)
setNoResult(newNotificationList.length == 0)
}
}, [notificationList, setLoading, unreadNotificationCount])
const onMarkAllAsRead = useCallback(async () => {
await updateReadAll(NotificationChannels.UiToast, true)
const list = notificationList.map((item: NotificationList) => {
if (!item.readed) {
item.readed = true
}
return item
})
setNotificationList(list)
setUnreadNotificationCount(0)
}, [notificationList])
const onMarkAsRead = useCallback(
async (id: string) => {
await updateRead(id, true)
const list = notificationList.map((item) => {
if (item.id === id) {
item.readed = true
}
return item
})
setNotificationList(list)
const unreadCount = notificationList.filter((item) => !item.readed).length
setUnreadNotificationCount(unreadCount)
},
[notificationList],
)
const getPushNotifications = async () => {
const desktopGranted =
'Notification' in window && window.Notification.permission === 'granted'
const channels = [NotificationChannels.UiToast]
if (desktopGranted) channels.push(NotificationChannels.Desktop)
const resp = await getList({
channels,
isListRequest: false,
isSent: false,
maxResultCount: 1000,
})
const items = resp.data.items ?? []
// Toast
const newToastList = items.filter(
(a) =>
a.notificationChannel === NotificationChannels.UiToast &&
!toastNotificationList.current.includes(a.id) &&
!a.isSent,
)
toastNotificationList.current = [
...toastNotificationList.current,
...newToastList.map((a) => a.id),
]
for (const notification of newToastList) {
toast.push(
{notification.message}
,
{ placement: 'bottom-end' },
)
await updateSent(notification.id, true)
await updateRead(notification.id, true)
}
// Desktop
if (desktopGranted) {
const newDesktopList = items.filter(
(a) =>
a.notificationChannel === NotificationChannels.Desktop &&
!desktopNotificationList.current.includes(a.id) &&
!a.isSent,
)
desktopNotificationList.current = [
...desktopNotificationList.current,
...newDesktopList.map((a) => a.id),
]
for (const notification of newDesktopList) {
const title = notification.notificationType || 'Bildirim'
const options = {
body: notification.message,
dir: 'ltr',
requireInteraction: true,
} as NotificationOptions
if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
const reg = await navigator.serviceWorker.ready
await reg.showNotification(title, options)
} else {
new window.Notification(title, options)
}
await updateSent(notification.id, true)
await updateRead(notification.id, true)
}
}
}
useEffect(() => {
if ('Notification' in window && window.Notification.permission === 'default') {
window.Notification.requestPermission()
}
getPushNotifications()
pushNotificationInterval.current = setInterval(() => {
if (tabHasFocusRef.current) {
getPushNotifications()
}
}, notificationInterval)
return () => {
clearInterval(pushNotificationInterval.current)
}
}, [])
return (
}
menuClass="p-0 min-w-[280px] md:min-w-[340px]"
placement={larger.md ? 'bottom-end' : 'bottom-center'}
onOpen={onNotificationOpen}
>
{translate('::Abp.Identity.ActivityLogs.Notifications')}
}
onClick={onMarkAllAsRead}
/>
{notificationList.length > 0 &&
notificationList.map((item, index) => (
onMarkAsRead(item.id)}
>
{notificationTypeAvatar(item.creatorId, item.tenantId)}
{item.notificationType && (
{item.notificationType}
)}
{item.message}
{dayjs(item.date).fromNow()}
))}
{loading && (
)}
{noResult && (
No notifications!
Please Try again later
)}
{translate('::Abp.Identity.ActivityLogs.ViewAllActivity')}
)
}
const Notification = withHeaderItem(_Notification)
export default Notification