Files
homelab-optimized/dashboard/ui/components/toast-provider.tsx
Gitea Mirror Bot e7652c8dab
Some checks failed
Documentation / Build Docusaurus (push) Failing after 5m3s
Documentation / Deploy to GitHub Pages (push) Has been skipped
Sanitized mirror from private repository - 2026-04-20 01:32:01 UTC
2026-04-20 01:32:01 +00:00

54 lines
1.7 KiB
TypeScript

"use client";
import { useState, useEffect, useCallback } from "react";
import { useSSE } from "@/lib/use-sse";
interface Toast {
id: number;
message: string;
type: "info" | "warning" | "error";
}
const ALERT_TYPES = ["container_unhealthy", "container_restarted", "drift_found"];
export function ToastProvider() {
const events = useSSE("/api/activity", 5);
const [toasts, setToasts] = useState<Toast[]>([]);
const [seen, setSeen] = useState(new Set<string>());
const addToast = useCallback((message: string, type: Toast["type"]) => {
const id = Date.now();
setToasts(prev => [...prev, { id, message, type }]);
setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), 5000);
}, []);
useEffect(() => {
for (const event of events) {
const key = `${event.type}-${event.timestamp}`;
if (seen.has(key)) continue;
if (ALERT_TYPES.includes(event.type)) {
setSeen(prev => new Set(prev).add(key));
addToast(event.raw || `${event.type}: ${event.source}`,
event.type.includes("unhealthy") ? "error" : "warning");
}
}
}, [events, seen, addToast]);
if (toasts.length === 0) return null;
const colors = {
info: "border-blue-500/20 bg-blue-500/5 backdrop-blur-xl",
warning: "border-amber-500/20 bg-amber-500/5 backdrop-blur-xl",
error: "border-red-500/20 bg-red-500/5 backdrop-blur-xl",
};
return (
<div className="fixed bottom-4 right-4 z-50 space-y-2 max-w-sm">
{toasts.map(t => (
<div key={t.id} className={`rounded-lg border px-4 py-3 text-xs shadow-lg backdrop-blur-md animate-slide-in ${colors[t.type]}`}>
<p className="text-foreground">{t.message}</p>
</div>
))}
</div>
);
}