import { useMutation, useSuspenseQuery } from "@tanstack/react-query"; import { toast } from "sonner"; import { useState } from "react"; import { deleteNotificationDestinationMutation, getNotificationDestinationOptions, testNotificationDestinationMutation, } from "~/client/api-client/@tanstack/react-query.gen"; import { Button } from "~/client/components/ui/button"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "~/client/components/ui/alert-dialog"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "~/client/components/ui/dropdown-menu"; import { Badge } from "~/client/components/ui/badge"; import { Card, CardTitle } from "~/client/components/ui/card"; import { Separator } from "~/client/components/ui/separator"; import { parseError } from "~/client/lib/errors"; import { cn } from "~/client/lib/utils"; import { Bell, Bot, ChevronDown, Globe, Hash, Key, Link, Lock, Mail, AtSign, AlertCircle, Clock, MessageSquare, Pencil, Server, Settings, Shield, Smile, TestTube2, Trash2, Users, } from "lucide-react"; import { useNavigate } from "@tanstack/react-router"; import type { GetNotificationDestinationResponse } from "~/client/api-client/types.gen"; import { useTimeFormat } from "~/client/lib/datetime"; type NotificationConfig = GetNotificationDestinationResponse["config"]; type NotificationStatus = GetNotificationDestinationResponse["status"]; type Props = { icon: React.ReactNode; label: string; value: string; mono?: boolean; }; function ConfigRow({ icon, label, value, mono }: Props) { return (
{icon} {label} {value}
); } function getStatusLabel(enabled: boolean, status: NotificationStatus) { return enabled ? status : "disabled"; } function getStatusClass(enabled: boolean, status: NotificationStatus) { if (!enabled) return "bg-gray-500"; if (status === "healthy") return "bg-success"; if (status === "error") return "bg-red-500"; return "bg-yellow-500"; } function NotificationConfigRows({ config }: { config: NotificationConfig }) { switch (config.type) { case "slack": return ( <> } label="Webhook URL" value={config.webhookUrl} mono /> } label="Bot Username" value={config.username || "—"} /> } label="Icon Emoji" value={config.iconEmoji || "—"} /> ); case "discord": return ( <> } label="Webhook URL" value={config.webhookUrl} mono /> } label="Username" value={config.username || "—"} /> } label="Avatar URL" value={config.avatarUrl || "—"} mono /> } label="Thread ID" value={config.threadId || "—"} /> ); case "email": return ( <> } label="SMTP Host" value={config.smtpHost} mono /> } label="SMTP Port" value={String(config.smtpPort)} /> } label="Username" value={config.username || "—"} /> } label="Password" value={config.password || "—"} /> } label="From" value={config.from} /> {config.fromName && ( } label="From Name" value={config.fromName} /> )} } label="To" value={config.to.join(", ")} /> } label="TLS" value={config.useTLS ? "Enabled" : "Disabled"} /> ); case "gotify": return ( <> } label="Server URL" value={config.serverUrl} mono /> } label="Token" value={config.token} mono /> } label="Path" value={config.path || "—"} mono /> } label="Priority" value={String(config.priority)} /> ); case "ntfy": return ( <> } label="Server URL" value={config.serverUrl || "Default (ntfy.sh)"} mono /> } label="Topic" value={config.topic} /> } label="Priority" value={config.priority} /> } label="Username" value={config.username || "—"} /> } label="Password" value={config.password || "—"} /> } label="Access Token" value={config.accessToken || "—"} /> ); case "pushover": return ( <> } label="User Key" value={config.userKey} mono /> } label="API Token" value={config.apiToken} mono /> } label="Devices" value={config.devices || "—"} /> } label="Priority" value={String(config.priority)} /> ); case "telegram": return ( <> } label="Bot Token" value={config.botToken} mono /> } label="Chat ID" value={config.chatId} mono /> } label="Thread ID" value={config.threadId || "—"} /> ); case "generic": return ( <> } label="URL" value={config.url} mono /> } label="Method" value={config.method} /> } label="Content Type" value={config.contentType || "—"} /> } label="JSON Mode" value={config.useJson ? "Enabled" : "Disabled"} /> } label="Title Key" value={config.titleKey || "—"} mono /> } label="Message Key" value={config.messageKey || "—"} mono /> {config.headers?.map((h, i) => ( } label={`Header ${i + 1}`} value={h} mono /> ))} ); case "custom": return } label="Shoutrrr URL" value={config.shoutrrrUrl} mono />; } } export function NotificationDetailsPage({ notificationId }: { notificationId: string }) { const navigate = useNavigate(); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const { formatDateTime } = useTimeFormat(); const { data } = useSuspenseQuery({ ...getNotificationDestinationOptions({ path: { id: notificationId } }), }); const deleteDestination = useMutation({ ...deleteNotificationDestinationMutation(), onSuccess: () => { toast.success("Notification destination deleted successfully"); void navigate({ to: "/notifications" }); }, onError: (error) => { toast.error("Failed to delete notification destination", { description: parseError(error)?.message, }); }, }); const testDestination = useMutation({ ...testNotificationDestinationMutation(), onSuccess: () => { toast.success("Test notification sent successfully"); }, onError: (error) => { toast.error("Failed to send test notification", { description: parseError(error)?.message, }); }, }); const handleConfirmDelete = () => { setShowDeleteConfirm(false); deleteDestination.mutate({ path: { id: String(data.id) } }); }; const handleTest = () => { testDestination.mutate({ path: { id: String(data.id) } }); }; return ( <>

{data.name}

{getStatusLabel(data.enabled, data.status)} {data.type}

Created {formatDateTime(data.createdAt)}

navigate({ to: `/notifications/${data.id}/edit` })}> Edit setShowDeleteConfirm(true)} disabled={deleteDestination.isPending} > Delete
Configuration
Delivery Health
} label="Status" value={getStatusLabel(data.enabled, data.status)} /> } label="Enabled" value={data.enabled ? "Yes" : "No"} /> } label="Last Checked" value={data.lastChecked ? formatDateTime(data.lastChecked) : "Never"} />
Last Delivery Error

{data.lastError}

Delete Notification Destination Are you sure you want to delete the notification destination "{data.name}"? This action cannot be undone and will remove this destination from all backup schedules. Cancel Delete ); }