feat: Show notice on new notifications (#8390)

This commit is contained in:
Stevche Radevski
2024-08-01 17:10:34 +02:00
committed by GitHub
parent dbd0b18726
commit d36d48df76
2 changed files with 80 additions and 18 deletions
@@ -1,6 +1,7 @@
import { import {
BellAlert, BellAlert,
BellAlertDone, BellAlertDone,
CircleFilledSolid,
InformationCircleSolid, InformationCircleSolid,
} from "@medusajs/icons" } from "@medusajs/icons"
import { Drawer, Heading, IconButton, Text } from "@medusajs/ui" import { Drawer, Heading, IconButton, Text } from "@medusajs/ui"
@@ -10,7 +11,7 @@ import { HttpTypes } from "@medusajs/types"
import { formatDistance } from "date-fns" import { formatDistance } from "date-fns"
import { InfiniteList } from "../../common/infinite-list" import { InfiniteList } from "../../common/infinite-list"
import { sdk } from "../../../lib/client" import { sdk } from "../../../lib/client"
import { notificationQueryKeys } from "../../../hooks/api" import { notificationQueryKeys, useNotifications } from "../../../hooks/api"
import { TFunction } from "i18next" import { TFunction } from "i18next"
import { FilePreview } from "../../common/file-preview" import { FilePreview } from "../../common/file-preview"
@@ -24,9 +25,17 @@ interface NotificationData {
} }
} }
const LAST_READ_NOTIFICATION_KEY = "notificationsLastReadAt"
export const Notifications = () => { export const Notifications = () => {
const [open, setOpen] = useState(false)
const { t } = useTranslation() 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(() => { useEffect(() => {
const onKeyDown = (e: KeyboardEvent) => { const onKeyDown = (e: KeyboardEvent) => {
@@ -42,14 +51,25 @@ export const Notifications = () => {
} }
}, []) }, [])
const handleOnOpen = (isOpen: boolean) => {
if (isOpen) {
setHasUnread(false)
setOpen(true)
localStorage.setItem(LAST_READ_NOTIFICATION_KEY, new Date().toISOString())
} else {
setOpen(false)
setLastReadAt(localStorage.getItem(LAST_READ_NOTIFICATION_KEY))
}
}
return ( return (
<Drawer open={open} onOpenChange={setOpen}> <Drawer open={open} onOpenChange={handleOnOpen}>
<Drawer.Trigger asChild> <Drawer.Trigger asChild>
<IconButton <IconButton
variant="transparent" variant="transparent"
className="text-ui-fg-muted hover:text-ui-fg-subtle" className="text-ui-fg-muted hover:text-ui-fg-subtle"
> >
<BellAlert /> {hasUnread ? <BellAlertDone /> : <BellAlert />}
</IconButton> </IconButton>
</Drawer.Trigger> </Drawer.Trigger>
<Drawer.Content> <Drawer.Content>
@@ -77,6 +97,10 @@ export const Notifications = () => {
<Notification <Notification
key={notification.id} key={notification.id}
notification={notification} notification={notification}
unread={
Date.parse(notification.created_at) >
(lastReadAt ? Date.parse(lastReadAt) : 0)
}
/> />
) )
}} }}
@@ -89,8 +113,10 @@ export const Notifications = () => {
const Notification = ({ const Notification = ({
notification, notification,
unread,
}: { }: {
notification: HttpTypes.AdminNotification notification: HttpTypes.AdminNotification
unread?: boolean
}) => { }) => {
const data = notification.data as unknown as NotificationData | undefined const data = notification.data as unknown as NotificationData | undefined
@@ -101,27 +127,35 @@ const Notification = ({
return ( return (
<> <>
<div className="flex items-start justify-center gap-3 border-b p-6"> <div className="flex items-start justify-center gap-3 border-b p-6 relative">
<div className="text-ui-fg-muted flex size-5 items-center justify-center"> <div className="text-ui-fg-muted flex size-5 items-center justify-center">
<InformationCircleSolid /> <InformationCircleSolid />
</div> </div>
<div className="flex w-full flex-col gap-y-3"> <div className="flex w-full flex-col gap-y-3">
<div> <div>
<div className="align-center flex flex-row justify-between"> <div className="items-center flex flex-row justify-between">
<Text size="small" leading="compact" weight="plus"> <Text size="small" leading="compact" weight="plus">
{data.title} {data.title}
</Text> </Text>
<Text <div className="items-center flex flex-row justify-center pr-4 relative">
as={"span"} <Text
className="text-ui-fg-subtle" as={"span"}
size="small" className="text-ui-fg-subtle"
leading="compact" size="small"
weight="plus" leading="compact"
> weight="plus"
{formatDistance(notification.created_at, new Date(), { >
addSuffix: true, {formatDistance(notification.created_at, new Date(), {
})} addSuffix: true,
</Text> })}
</Text>
{unread && (
<CircleFilledSolid
className="text-ui-fg-interactive absolute top-1 right-0"
style={{ transform: "scale(0.3)" }}
/>
)}
</div>
</div> </div>
{!!data.description && ( {!!data.description && (
<Text <Text
@@ -161,3 +195,31 @@ const NotificationsEmptyState = ({ t }: { t: TFunction }) => {
</div> </div>
) )
} }
const useUnreadNotifications = () => {
const [hasUnread, setHasUnread] = useState(false)
const { notifications } = useNotifications(
{ limit: 1, offset: 0, fields: "created_at" },
{ refetchInterval: 3000 }
)
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
}
@@ -11,5 +11,5 @@ export interface AdminNotification {
original_notification_id?: string | null original_notification_id?: string | null
external_id?: string | null external_id?: string | null
provider_id: string provider_id: string
created_at: Date created_at: string
} }