src/components/notification/Notification.tsx
import { BellRing } from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
import { FaCheckDouble } from 'react-icons/fa6';
import { io } from 'socket.io-client';
import { toast } from 'sonner';
import { NotificationTypes } from '../../@types/notification';
import { UserInfoTypes } from '../../@types/userType';
import useToken from '../../hooks/useToken';
import {
addNotification,
markAllRead,
userNotification,
} from '../../redux/features/notificationSlice';
import { useAppDispatch, useAppSelector } from '../../redux/hooks/hooks';
import { notificationSound } from '../../utils/images';
import fetchInfo from '../../utils/userDetails';
import NotificationItem from '../cards/NotificationItem';
import { DynamicData } from '../../@types/DynamicData';
import NotificationSkeleton from '../cards/NotificationSkeleton';
const Notification = () => {
const dispatch = useAppDispatch();
const { accessToken } = useToken();
const notificationPlayer = useRef<HTMLAudioElement>(null);
const [notificationActive, setNotificationActive] = useState(false);
const { notifications, value, isLoading } = useAppSelector(
(state) => state.notifications,
);
const playAudio = () => {
notificationPlayer.current?.play();
};
useEffect(() => {
if (accessToken) {
const { id } = fetchInfo() as UserInfoTypes;
const socket = io(`${import.meta.env.VITE_API_APP_ROOT_URL}`, {
transports: ['websocket'],
auth: {
token: accessToken,
},
});
socket.on('notifications', (data) => {
dispatch(userNotification(data));
});
socket.on(`notification-${id}`, (notification) => {
if (notification) {
dispatch(addNotification(notification));
toast.success('You have new notification 🔔');
playAudio();
}
});
return () => {
socket.disconnect();
};
}
}, [accessToken, dispatch]);
const sortedNotification = sortNotificationsByDate(
notifications as unknown as NotificationTypes[],
);
const readAllNotification = async () => {
try {
await dispatch(markAllRead()).unwrap();
} catch (e) {
const err = e as DynamicData;
toast.error(
err?.data?.message ||
err?.message ||
'Unknown error occurred! Please try again!',
);
}
};
return (
<div className="relative flex-center transition-colors border border-neutral-grey p-2 h-max rounded-lg cursor-pointer hover:bg-neutral-grey/20">
<BellRing
size={18}
onClick={() => setNotificationActive((prev) => !prev)}
className="cursor-pointer"
role="img"
aria-label="bell-image"
/>
<audio ref={notificationPlayer} src={notificationSound} />
{value > 0 && (
<div
className="absolute -top-4 -right-4 bg-action-error w-8 text-xs h-8 text-neutral-white rounded-full flex-center"
aria-label="notification-number"
>
{value <= 99 ? value : '99+'}
</div>
)}
{notificationActive && (
<div
className="absolute top-full -right-[100px] ipad:-right-[50px] mt-5 p-2 bg-neutral-white shadow-custom-heavy rounded-xl w-[80vw] ipad:w-[350px] h-max max-h-[80vh] md:max-h-[70vh] overflow-y-scroll no-scrollbar z-50"
aria-label="notification-tab"
>
<div className="w-full h-full overflow-hidden">
<h2 className="text-md ipad:text-lg font-semibold text-start pl-4 pt-4">
Notifications
</h2>
<div className="flex items-center justify-end p-5">
<div
onClick={readAllNotification}
aria-label="mark-button"
className={`flex items-center gap-2 text-[11px] text-neutral-black bg-primary-lightblue/20 px-3 py-1 rounded-full hover:text-neutral-black/50 ${notifications?.filter((not) => not.unread === true).length === 0 && 'hidden'}`}
>
Mark all as read
<FaCheckDouble fill={'black'} />
</div>
</div>
<div className="w-full flex-1 h-[90%] no-scrollbar px-2 pb-10">
{isLoading ? (
Array.from({ length: 12 }).map((_, i) => (
<NotificationSkeleton key={i} />
))
) : sortedNotification && sortedNotification.length > 0 ? (
sortedNotification.map((item) => (
<NotificationItem
key={item.id}
id={item.id}
unread={item.unread}
text={item.message}
date={`${item.createdAt.getHours()}:${item.createdAt.getMinutes() < 10 ? `0${item.createdAt.getMinutes()}` : item.createdAt.getMinutes()} - ${item.createdAt.getDate()}/${item.createdAt.getMonth()}`}
/>
))
) : (
<div className="text-xs p-4 text-center">
No new notification available!
</div>
)}
</div>
</div>
</div>
)}
</div>
);
};
function sortNotificationsByDate(
noties: NotificationTypes[],
): NotificationTypes[] {
return noties
.map((notification) => ({
...notification,
createdAt: new Date(notification.createdAt),
}))
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
}
export default Notification;