chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
import { useFetcher } from "@remix-run/react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import simplur from "simplur";
|
||||
import { NotificationIcon } from "~/assets/icons/NotificationIcon";
|
||||
import { Button } from "~/components/primitives/Buttons";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "~/components/primitives/Popover";
|
||||
import { SimpleTooltip } from "~/components/primitives/Tooltip";
|
||||
import { usePlatformNotifications } from "~/routes/resources.platform-notifications";
|
||||
import { NotificationCard } from "./NotificationCard";
|
||||
|
||||
type Notification = {
|
||||
id: string;
|
||||
friendlyId: string;
|
||||
scope: string;
|
||||
priority: number;
|
||||
payload: {
|
||||
version: string;
|
||||
data: {
|
||||
title: string;
|
||||
description: string;
|
||||
image?: string;
|
||||
actionLabel?: string;
|
||||
actionUrl?: string;
|
||||
dismissOnAction?: boolean;
|
||||
};
|
||||
};
|
||||
isRead: boolean;
|
||||
};
|
||||
|
||||
export function NotificationPanel({
|
||||
isCollapsed,
|
||||
hasIncident,
|
||||
organizationId,
|
||||
projectId,
|
||||
}: {
|
||||
isCollapsed: boolean;
|
||||
hasIncident: boolean;
|
||||
organizationId: string;
|
||||
projectId: string;
|
||||
}) {
|
||||
const { notifications } = usePlatformNotifications(organizationId, projectId) as {
|
||||
notifications: Notification[];
|
||||
};
|
||||
const [dismissedIds, setDismissedIds] = useState<Set<string>>(new Set());
|
||||
const dismissFetcher = useFetcher();
|
||||
const seenIdsRef = useRef<Set<string>>(new Set());
|
||||
const seenFetcher = useFetcher();
|
||||
const clickedIdsRef = useRef<Set<string>>(new Set());
|
||||
const clickFetcher = useFetcher();
|
||||
|
||||
const visibleNotifications = notifications.filter((n) => !dismissedIds.has(n.id));
|
||||
const notification = visibleNotifications[0] ?? null;
|
||||
|
||||
const handleDismiss = useCallback((id: string) => {
|
||||
setDismissedIds((prev) => new Set(prev).add(id));
|
||||
|
||||
dismissFetcher.submit(
|
||||
{},
|
||||
{
|
||||
method: "POST",
|
||||
action: `/resources/platform-notifications/${id}/dismiss`,
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
const fireClickBeacon = useCallback((id: string) => {
|
||||
if (clickedIdsRef.current.has(id)) return;
|
||||
clickedIdsRef.current.add(id);
|
||||
|
||||
clickFetcher.submit(
|
||||
{},
|
||||
{
|
||||
method: "POST",
|
||||
action: `/resources/platform-notifications/${id}/clicked`,
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
// Fire seen beacon
|
||||
const fireSeenBeacon = useCallback((n: Notification) => {
|
||||
if (seenIdsRef.current.has(n.id)) return;
|
||||
seenIdsRef.current.add(n.id);
|
||||
|
||||
seenFetcher.submit(
|
||||
{},
|
||||
{
|
||||
method: "POST",
|
||||
action: `/resources/platform-notifications/${n.id}/seen`,
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
// Beacon current notification on mount
|
||||
useEffect(() => {
|
||||
if (notification && !hasIncident) {
|
||||
fireSeenBeacon(notification);
|
||||
}
|
||||
}, [notification?.id, hasIncident]);
|
||||
|
||||
if (!notification) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { title, description, image, actionUrl, dismissOnAction } = notification.payload.data;
|
||||
const card = (
|
||||
<NotificationCard
|
||||
title={title}
|
||||
description={description}
|
||||
image={image}
|
||||
actionUrl={actionUrl}
|
||||
onDismiss={() => handleDismiss(notification.id)}
|
||||
onCardClick={() => {
|
||||
fireClickBeacon(notification.id);
|
||||
if (dismissOnAction) {
|
||||
handleDismiss(notification.id);
|
||||
}
|
||||
}}
|
||||
onLinkClick={() => fireClickBeacon(notification.id)}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<div className={isCollapsed ? "p-1" : "p-2"}>
|
||||
{isCollapsed ? (
|
||||
<SimpleTooltip
|
||||
asChild
|
||||
button={
|
||||
<div className="relative">
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="small-menu-item" className="h-8 w-8.75 justify-center">
|
||||
<NotificationIcon className="size-5 text-success" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</div>
|
||||
}
|
||||
content={simplur`${visibleNotifications.length} notification[|s]`}
|
||||
side="right"
|
||||
sideOffset={8}
|
||||
disableHoverableContent
|
||||
/>
|
||||
) : (
|
||||
card
|
||||
)}
|
||||
</div>
|
||||
<PopoverContent side="right" sideOffset={8} align="end" className="w-56 min-w-0! p-0">
|
||||
{card}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user