import { BellAlert, BellAlertDone, InformationCircleSolid, } from "@medusajs/icons" import { HttpTypes } from "@medusajs/types" import { clx, Drawer, Heading, IconButton, Text } from "@medusajs/ui" import { formatDistance } from "date-fns" import { TFunction } from "i18next" import { useEffect, useState } from "react" import { useTranslation } from "react-i18next" import { notificationQueryKeys, useNotifications } from "../../../hooks/api" import { sdk } from "../../../lib/client" import { FilePreview } from "../../common/file-preview" import { InfiniteList } from "../../common/infinite-list" interface NotificationData { title: string description?: string file?: { filename?: string url?: string mimeType?: string } } const LAST_READ_NOTIFICATION_KEY = "notificationsLastReadAt" export const Notifications = () => { const { t } = useTranslation() const [open, setOpen] = useState(false) const [hasUnread, setHasUnread] = useUnreadNotifications() // This is used to show the unread icon on the notification when the drawer is open, // so it should lag behind the local storage data and should only be reset on close const [lastReadAt, setLastReadAt] = useState( localStorage.getItem(LAST_READ_NOTIFICATION_KEY) ) useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if (e.key === "n" && (e.metaKey || e.ctrlKey)) { setOpen((prev) => !prev) } } document.addEventListener("keydown", onKeyDown) return () => { document.removeEventListener("keydown", onKeyDown) } }, []) const handleOnOpen = (shouldOpen: boolean) => { if (shouldOpen) { setHasUnread(false) setOpen(true) localStorage.setItem(LAST_READ_NOTIFICATION_KEY, new Date().toISOString()) } else { setOpen(false) setLastReadAt(localStorage.getItem(LAST_READ_NOTIFICATION_KEY)) } } return ( {hasUnread ? : } {t("notifications.domain")} {t("notifications.accessibility.description")} responseKey="notifications" queryKey={notificationQueryKeys.all} queryFn={(params) => sdk.admin.notification.list({ ...params, channel: "feed", }) } queryOptions={{ enabled: open }} renderEmpty={() => } renderItem={(notification) => { return ( (lastReadAt ? Date.parse(lastReadAt) : 0) } /> ) }} /> ) } const Notification = ({ notification, unread, }: { notification: HttpTypes.AdminNotification unread?: boolean }) => { const data = notification.data as unknown as NotificationData | undefined // We need at least the title to render a notification in the feed if (!data?.title) { return null } return ( <>
{data.title}
{formatDistance(notification.created_at, new Date(), { addSuffix: true, })} {unread && (
)}
{!!data.description && ( {data.description} )}
{!!data?.file?.url && ( )}
) } const NotificationsEmptyState = ({ t }: { t: TFunction }) => { return (
{t("notifications.emptyState.title")} {t("notifications.emptyState.description")}
) } const useUnreadNotifications = () => { const [hasUnread, setHasUnread] = useState(false) const { notifications } = useNotifications( { limit: 1, offset: 0, fields: "created_at" }, { refetchInterval: 60_000 } ) const lastNotification = notifications?.[0] useEffect(() => { if (!lastNotification) { return } const lastNotificationAsTimestamp = Date.parse(lastNotification.created_at) const lastReadDatetime = localStorage.getItem(LAST_READ_NOTIFICATION_KEY) const lastReadAsTimestamp = lastReadDatetime ? Date.parse(lastReadDatetime) : 0 if (lastNotificationAsTimestamp > lastReadAsTimestamp) { setHasUnread(true) } }, [lastNotification]) return [hasUnread, setHasUnread] as const }