Files
starr-philproperties/src/components/generic/AdminStickyAnnouncementBar.jsx
T
kennethobsequio 71f758fe0b merged
Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
2026-07-11 12:12:40 +08:00

117 lines
4.4 KiB
React

import { useCallback, useState } from "react";
import { X } from "lucide-react";
import { useNavigate } from "react-router-dom";
import { Button } from "@/components/ui/button";
import {
AlertDialog, AlertDialogAction, AlertDialogCancel,
AlertDialogContent, AlertDialogDescription,
AlertDialogFooter, AlertDialogHeader, AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { useAdminNotifications } from "@/contexts/AdminNotificationContext";
import { NotificationIcon, getTypeAccent, resolveStickyStyle } from "@/components/generic/notificationDisplay";
// Admin counterpart to StickyAnnouncementBar (client). Only supports the
// explicit link_url from the "On Open" section — the type-based fallback
// resolver in notificationDisplay.jsx points at client-only routes
// (/course/:id, /plans, /group/:id), which don't exist in the admin app.
export default function AdminStickyAnnouncementBar() {
const navigate = useNavigate();
const { stickyAnnouncement, markSeen } = useAdminNotifications();
const [detailsOpen, setDetailsOpen] = useState(false);
const onDismiss = useCallback(async () => {
if (!stickyAnnouncement) return;
await markSeen(stickyAnnouncement.notification_id);
}, [stickyAnnouncement, markSeen]);
// Opening the dialog must NOT mark it seen — markSeen clears
// stickyAnnouncement, which would unmount this component (dialog included)
// before it ever shows. Only the X button dismisses/marks seen.
const onClickBanner = useCallback(() => {
if (!stickyAnnouncement) return;
setDetailsOpen(true);
}, [stickyAnnouncement]);
if (!stickyAnnouncement) return null;
const accentClass = getTypeAccent(stickyAnnouncement.type);
const inlineStyle = resolveStickyStyle(stickyAnnouncement.data);
const linkUrl = stickyAnnouncement.data?.linkUrl || null;
const openLink = () => {
if (!linkUrl) return;
if (linkUrl.startsWith("/")) navigate(linkUrl);
else window.open(linkUrl, "_blank", "noopener,noreferrer");
};
return (
<>
<div
role="status"
className="w-full border-b shadow-sm px-4 md:px-6"
>
<div
onClick={onClickBanner}
className="w-full cursor-pointer bg-card rounded-none py-3 flex items-center justify-between gap-4"
style={inlineStyle ?? undefined}
>
<div className="flex items-start gap-3 min-w-0">
<div className={`flex h-9 w-9 items-center justify-center rounded ${accentClass}`}>
<NotificationIcon type={stickyAnnouncement.type} className="h-5 w-5" />
</div>
<div className="min-w-0">
<p className="text-sm font-semibold leading-snug truncate">
{stickyAnnouncement.title || "Announcement"}
</p>
<p className="text-sm text-muted-foreground leading-snug line-clamp-2">
{stickyAnnouncement.message || ""}
</p>
</div>
</div>
<Button
variant="outline"
size="icon"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
void onDismiss();
}}
aria-label="Dismiss sticky announcement"
title="Dismiss"
>
<X className="size-4" />
</Button>
</div>
</div>
<AlertDialog open={detailsOpen} onOpenChange={setDetailsOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="flex items-center gap-2">
<div className={`flex h-8 w-8 items-center justify-center rounded shrink-0 ${accentClass}`}>
<NotificationIcon type={stickyAnnouncement.type} className="h-4 w-4" />
</div>
{stickyAnnouncement.title || "Announcement"}
</AlertDialogTitle>
<AlertDialogDescription asChild>
<p className="whitespace-pre-wrap text-left pt-1 text-foreground">
{stickyAnnouncement.message || ""}
</p>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Close</AlertDialogCancel>
{linkUrl && (
<AlertDialogAction onClick={openLink}>
Open Link
</AlertDialogAction>
)}
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
);
}