169 lines
4.6 KiB
JavaScript
169 lines
4.6 KiB
JavaScript
import { useRouter } from "next/router";
|
|
import { memo, useCallback, useEffect, useRef, useState } from "react";
|
|
import {
|
|
notificationService,
|
|
NotificationType,
|
|
} from "../services/notification.service.js";
|
|
import styles from "../styles/exos/Notifications.module.scss";
|
|
import { MdDone, MdError, MdInfo } from "react-icons/md";
|
|
import { BiError } from "react-icons/bi";
|
|
|
|
function NotificationComp({
|
|
notification,
|
|
classes,
|
|
removeAlert,
|
|
mount,
|
|
fadeIn,
|
|
}) {
|
|
const notifIcon = {
|
|
[NotificationType.Success]: <MdDone />,
|
|
[NotificationType.Warning]: <BiError />,
|
|
[NotificationType.Info]: <MdInfo />,
|
|
[NotificationType.Error]: <MdError />,
|
|
};
|
|
return (
|
|
<div
|
|
className={classes + " " + (mount && styles["fadeOut"])}
|
|
onClick={() => {
|
|
removeAlert(notification);
|
|
}}
|
|
>
|
|
<div className={styles['notif-content']}>
|
|
<div className={styles["notif-title"]}>
|
|
{notifIcon[notification.type]} {notification.title}
|
|
</div>
|
|
<div className={styles["notif-msg"]}>{notification.message}</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
const Notification = memo(NotificationComp);
|
|
|
|
export default function Notifications({ id = "default-notif", fade = true }) {
|
|
const mounted = useRef(false);
|
|
const router = useRouter();
|
|
const [notification, setNotif] = useState([]);
|
|
|
|
useEffect(() => {
|
|
mounted.current = true;
|
|
|
|
// subscribe to new alert notifications
|
|
const subscription = notificationService.onAlert(id).subscribe((notif) => {
|
|
// clear alerts when an empty alert is received
|
|
if (!notif.message) {
|
|
setNotif((alerts) => {
|
|
// filter out alerts without 'keepAfterRouteChange' flag
|
|
const filteredAlerts = alerts.filter((x) => x.keepAfterRouteChange);
|
|
|
|
// remove 'keepAfterRouteChange' flag on the rest
|
|
return omit(filteredAlerts, "keepAfterRouteChange");
|
|
});
|
|
} else {
|
|
// add alert to array with unique id
|
|
notif.itemId = Math.random();
|
|
|
|
addNotif(notif);
|
|
|
|
// auto close alert if required
|
|
if (notif.autoClose) {
|
|
setTimeout(() => removeAlert(notif), 3000);
|
|
}
|
|
}
|
|
});
|
|
|
|
// clear alerts on location change
|
|
const clearAlerts = () => notificationService.clear(id);
|
|
router.events.on("routeChangeStart", clearAlerts);
|
|
|
|
// clean up function that runs when the component unmounts
|
|
return () => {
|
|
mounted.current = false;
|
|
|
|
// unsubscribe to avoid memory leaks
|
|
subscription.unsubscribe();
|
|
router.events.off("routeChangeStart", clearAlerts);
|
|
};
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
function omit(arr, key) {
|
|
return arr.map((obj) => {
|
|
const { [key]: omitted, ...rest } = obj;
|
|
return rest;
|
|
});
|
|
}
|
|
|
|
function addNotif(notif) {
|
|
if (notification.length > 5) {
|
|
removeAlert(notification[0]);
|
|
}
|
|
setNotif((alerts) => [...alerts, notif]);
|
|
}
|
|
|
|
function removeAlertFunc(notif) {
|
|
if (!mounted.current) return;
|
|
if (fade) {
|
|
// fade out alert
|
|
setNotif((alerts) =>
|
|
alerts.map((x) =>
|
|
x.itemId === notif.itemId ? { ...x, fade: true } : x
|
|
)
|
|
);
|
|
|
|
// remove alert after faded out
|
|
setTimeout(() => {
|
|
setNotif((alerts) => alerts.filter((x) => x.itemId !== notif.itemId));
|
|
}, 500);
|
|
} else {
|
|
// remove alert
|
|
setNotif((alerts) => alerts.filter((x) => x.itemId !== notif.itemId));
|
|
}
|
|
}
|
|
const removeAlert = useCallback(removeAlertFunc, [fade]);
|
|
|
|
function cssClasses(notif) {
|
|
if (!notif) return;
|
|
|
|
const classes = [styles["notif"], styles["notif-dismissable"]];
|
|
|
|
const notifTypeClass = {
|
|
[NotificationType.Success]: styles["notif-success"],
|
|
[NotificationType.Error]: styles["notif-danger"],
|
|
[NotificationType.Info]: styles["notif-info"],
|
|
[NotificationType.Warning]: styles["notif-warning"],
|
|
};
|
|
classes.push(notifTypeClass[notif.type]);
|
|
|
|
if (notif.fade) {
|
|
classes.push("fade");
|
|
}
|
|
|
|
return classes.join(" ");
|
|
}
|
|
useEffect(() => {
|
|
if (!mounted.current) return;
|
|
if (notification.length > 5) {
|
|
removeAlert(notification[0]);
|
|
}
|
|
}, [notification, removeAlert]);
|
|
|
|
if (!notification.length) return null;
|
|
|
|
return (
|
|
<div className={styles["notifications-container"]}>
|
|
{notification.map((notification, index) => {
|
|
return (
|
|
<Notification
|
|
removeAlert={removeAlert}
|
|
notification={notification}
|
|
classes={cssClasses(notification)}
|
|
key={index}
|
|
mount={notification.fade}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|