import { useEffect, useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { House, Pencil, Bell, Lock, Send, Clock3, Plus, Trash2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Spinner } from "@/components/ui/spinner"; import { Separator } from "@/components/ui/separator"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import AppBreadcrumb from "@/components/generic/Breadcrumb/AppBreadcrumb"; import { PageMeta } from "@/contexts/MetadataContext"; import { AdminNotificationTemplateProvider, useAdminNotificationTemplates, } from "@/contexts/AdminNotificationTemplateContext"; import { NOTIFICATION_TEMPLATE_TYPES, getNotificationTemplateType } from "@/data/notificationTemplateTypes.data"; import { STATUS_META, hasPendingChanges } from "@/data/notificationTemplateStatus.data"; import { cn } from "@/lib/utils"; function TemplateCard({ item, onEdit, onDelete }) { const typeMeta = getNotificationTemplateType(item.notify_type); const TypeIcon = typeMeta?.icon ?? Bell; const status = STATUS_META[item.status] ?? STATUS_META.draft; const pending = hasPendingChanges(item); return (
{!item.is_system && ( )}

{item.label}

{item.is_system && ( System )}
{item.type}

{item.title || item.draft_title || "No title yet"}

{typeMeta && ( {typeMeta.label} )} {status.label} {pending && ( Pending changes )}
); } function NotificationTemplatesInner() { const navigate = useNavigate(); const { templates, loading, fetchTemplates, deleteTemplate } = useAdminNotificationTemplates(); const [activeType, setActiveType] = useState("all"); const [deleteTarget, setDeleteTarget] = useState(null); useEffect(() => { fetchTemplates(); }, []); const handleDelete = async () => { if (!deleteTarget) return; await deleteTemplate(deleteTarget.notification_template_id); setDeleteTarget(null); }; const filtered = useMemo( () => activeType === "all" ? templates : templates.filter((t) => t.notify_type === activeType), [templates, activeType] ); const typesInUse = useMemo( () => NOTIFICATION_TEMPLATE_TYPES.filter((t) => templates.some((tpl) => tpl.notify_type === t.value)), [templates] ); return (
, to: "/admin" }, { label: "Announcements", to: "/admin/announcements" }, { label: "Templates" }, ]} />

Announcement Templates

Title and message wording for every automated notification STARR sends.

{templates.filter((t) => t.status === "sent").length} sent {templates.filter((t) => t.status === "draft").length} draft {templates.some(hasPendingChanges) && ( {templates.filter(hasPendingChanges).length} with pending changes )}

System templates are locked — code fires them by referencing their exact type, so only the title and message wording is editable, never the type itself, and they can't be deleted.

Custom templates (no lock icon) are reusable title/message presets you create — pick one from the "Load from template" dropdown when composing a new announcement to skip retyping recurring wording. You can freely create, edit, and delete these.

Draft vs. Sent (system templates only): a Sent template is the version actually used for real notifications right now. Editing a Sent template doesn't change what goes out immediately — it's held as a pending change until you press{" "} Publish again.

{typesInUse.map((t) => { const Icon = t.icon; const count = templates.filter((tpl) => tpl.notify_type === t.value).length; return ( ); })}
{loading && !templates.length ? (
) : !filtered.length ? (

No notification templates found.

) : (
{filtered.map((item) => ( navigate(`/admin/announcement-templates/${t.notification_template_id}/edit`)} onDelete={setDeleteTarget} /> ))}
)}
!open && setDeleteTarget(null)}> Delete this template? "{deleteTarget?.label}" will be permanently removed. It won't affect any announcements already sent. Cancel {loading ? : } Delete
); } export default function NotificationTemplates() { return ( ); }