1
0
Fork 0

fix(notifications): add per-row mark as read

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Jason Fraley 2026-09-01 17:17:41 -04:00
parent 36ab2eba9b
commit 3d8655aa8a

View file

@ -3,7 +3,7 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { BellIcon } from "lucide-react";
import { BellIcon, CheckIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
@ -83,26 +83,28 @@ export function NotificationsBell() {
};
}, [fetchNotifications]);
async function handleItemClick(item: NotificationItem) {
if (marking) return;
if (!item.read) {
setMarking(true);
setActionError(null);
try {
const result = await markNotificationsRead([item.id]);
if (result.success) {
setNotifications((prev) =>
prev.map((n) => (n.id === item.id ? { ...n, read: true } : n)),
);
} else {
setActionError(result.error ?? "Failed to update notifications.");
}
} catch (err) {
setActionError(err instanceof Error ? err.message : "Failed to update notifications.");
} finally {
setMarking(false);
async function handleMarkOne(item: NotificationItem) {
if (item.read || marking) return;
setMarking(true);
setActionError(null);
try {
const result = await markNotificationsRead([item.id]);
if (result.success) {
setNotifications((prev) =>
prev.map((n) => (n.id === item.id ? { ...n, read: true } : n)),
);
} else {
setActionError(result.error ?? "Failed to update notifications.");
}
} catch (err) {
setActionError(err instanceof Error ? err.message : "Failed to update notifications.");
} finally {
setMarking(false);
}
}
async function handleItemClick(item: NotificationItem) {
if (!item.read) await handleMarkOne(item);
if (item.link) {
router.push(item.link);
setOpen(false);
@ -194,8 +196,24 @@ export function NotificationsBell() {
)}
{item.title}
</span>
<span className="shrink-0 text-[10px] text-muted-foreground">
{timeAgo(item.createdAt)}
<span className="flex shrink-0 items-center gap-1.5">
{!item.read && (
<button
type="button"
title="Mark as read"
aria-label={`Mark "${item.title}" as read`}
onClick={(e) => {
e.stopPropagation();
void handleMarkOne(item);
}}
className="rounded-sm p-0.5 text-muted-foreground/60 transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
>
<CheckIcon className="size-3.5" />
</button>
)}
<span className="text-[10px] text-muted-foreground">
{timeAgo(item.createdAt)}
</span>
</span>
</span>
{item.message && (