Generateurv2/frontend/components/Notifications.jsx
2022-06-24 13:42:16 +02:00

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>
);
}