diff --git a/src/app/components/ErrorLogBell.tsx b/src/app/components/ErrorLogBell.tsx
new file mode 100644
index 0000000..bce32da
--- /dev/null
+++ b/src/app/components/ErrorLogBell.tsx
@@ -0,0 +1,136 @@
+"use client";
+
+import { useState } from "react";
+import { Bell } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import { ScrollArea } from "@/components/ui/scroll-area";
+import { errorToast } from "@/lib/errorReporter";
+import { formatBadgeCount } from "@/lib/errorLogUnread";
+import { formatFullTime, formatTime } from "@/lib/time";
+import { useErrorLogs, type ErrorLogEntry } from "@/app/hooks/useErrorLogs";
+
+function ErrorLogRow({ entry }: { entry: ErrorLogEntry }) {
+ const created = new Date(entry.createdAt);
+ return (
+
+
+
+ {entry.source}
+
+
+
+
+ {entry.message}
+
+ {entry.details && (
+
+ {entry.details}
+
+ )}
+
+ );
+}
+
+export function ErrorLogBell() {
+ const { logs, unread, loading, markAllRead, clearAll } = useErrorLogs();
+ const [open, setOpen] = useState(false);
+ const [confirmingClear, setConfirmingClear] = useState(false);
+ const [clearing, setClearing] = useState(false);
+
+ const badge = formatBadgeCount(unread);
+
+ const handleOpenChange = (next: boolean) => {
+ setOpen(next);
+ setConfirmingClear(false);
+ if (next) markAllRead();
+ };
+
+ const handleClear = async () => {
+ if (!confirmingClear) {
+ setConfirmingClear(true);
+ return;
+ }
+ setClearing(true);
+ try {
+ await clearAll();
+ setConfirmingClear(false);
+ } catch (error) {
+ errorToast(
+ "errors.clear",
+ error instanceof Error ? error.message : "Couldn't clear error logs."
+ );
+ } finally {
+ setClearing(false);
+ }
+ };
+
+ return (
+
+
+
+
+
+
+ Error log
+
+ {loading && logs.length === 0 ? (
+
+ Loading...
+
+ ) : logs.length === 0 ? (
+
+ No errors logged.
+
+ ) : (
+
+
+ {logs.map((entry) => (
+
+ ))}
+
+
+ )}
+ {logs.length > 0 && (
+
+
+
+ )}
+
+
+ );
+}