// Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 import { atoms, getApi } from "@/store/global"; import { useAtom, useAtomValue } from "jotai"; import { useCallback, useEffect, useState } from "react"; const notificationActions: { [key: string]: () => void } = { installUpdate: () => { getApi().installAppUpdate(); }, // Add other action functions here }; export function useNotification() { const notificationPopoverMode = useAtomValue(atoms.notificationPopoverMode); const [notifications, setNotifications] = useAtom(atoms.notifications); const [hoveredId, setHoveredId] = useState(null); const removeNotification = useCallback( (id: string) => { setNotifications((prevNotifications) => prevNotifications.filter((n) => n.id !== id)); }, [setNotifications] ); const hideNotification = useCallback( (id: string) => { setNotifications((prevNotifications) => prevNotifications.map((n) => (n.id === id ? { ...n, hidden: true } : n)) ); }, [setNotifications] ); const hideAllNotifications = useCallback(() => { setNotifications((prevNotifications) => prevNotifications.map((n) => ({ ...n, hidden: true }))); }, [setNotifications]); const removeAllNotifications = useCallback(() => { setNotifications((prevNotifications) => prevNotifications.filter((n) => n.persistent)); }, [setNotifications]); const copyNotification = useCallback( (id: string) => { const notif = notifications.find((n) => n.id === id); if (!notif) return; let text = notif.title ?? ""; if (notif.message) { text += text.length > 0 ? `\n${notif.message}` : notif.message; } navigator.clipboard .writeText(text) .then(() => { console.info("Text copied to clipboard"); }) .catch((err) => { console.error("Failed to copy text: ", err); }); }, [notifications] ); const handleActionClick = useCallback( (e: React.MouseEvent, action: NotificationActionType, id: string) => { e.stopPropagation(); const actionFn = notificationActions[action.actionKey]; if (actionFn) { actionFn(); removeNotification(id); } else { console.warn(`No action found for key: ${action.actionKey}`); } }, [removeNotification] ); useEffect(() => { if (notificationPopoverMode) { return; } const hasExpiringNotifications = notifications.some((notif) => notif.expiration); if (!hasExpiringNotifications) { return; } const intervalId = setInterval(() => { const now = Date.now(); setNotifications((prevNotifications) => prevNotifications.filter( (notif) => !notif.expiration || notif.expiration > now || notif.id === hoveredId ) ); }, 1000); return () => clearInterval(intervalId); }, [notificationPopoverMode, notifications, hoveredId, setNotifications]); const formatTimestamp = (timestamp: string): string => { const notificationTime = new Date(timestamp).getTime(); const now = Date.now(); const diffInSeconds = Math.floor((now - notificationTime) / 1000); const diffInMinutes = Math.floor(diffInSeconds / 60); const diffInHours = Math.floor(diffInMinutes / 60); const diffInDays = Math.floor(diffInHours / 24); if (diffInMinutes === 0) { return `Just now`; } else if (diffInMinutes < 60) { return `${diffInMinutes} mins ago`; } else if (diffInHours < 24) { return `${diffInHours} hrs ago`; } else if (diffInDays < 7) { return `${diffInDays} days ago`; } else { return new Date(timestamp).toLocaleString(); } }; return { notifications, hoveredId, setHoveredId, removeNotification, removeAllNotifications, hideNotification, hideAllNotifications, copyNotification, handleActionClick, formatTimestamp, }; }