fix(ui): clickable unread dot in inbox with fade-out, remove empty circle for read issues

In the My Recent Issues section, the blue unread dot is now a button that
marks the issue as read on click with a smooth opacity fade-out. Already-read
issues show empty space instead of a hollow circle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dotta
2026-03-06 14:27:12 -06:00
parent 5aecb148a2
commit 54a4f784a4

View File

@@ -498,6 +498,31 @@ export function Inbox() {
}, },
}); });
const [fadingOutIssues, setFadingOutIssues] = useState<Set<string>>(new Set());
const markReadMutation = useMutation({
mutationFn: (id: string) => issuesApi.markRead(id),
onMutate: (id) => {
setFadingOutIssues((prev) => new Set(prev).add(id));
},
onSuccess: () => {
if (selectedCompanyId) {
queryClient.invalidateQueries({ queryKey: queryKeys.issues.listTouchedByMe(selectedCompanyId) });
queryClient.invalidateQueries({ queryKey: queryKeys.issues.listUnreadTouchedByMe(selectedCompanyId) });
queryClient.invalidateQueries({ queryKey: queryKeys.sidebarBadges(selectedCompanyId) });
}
},
onSettled: (_data, _error, id) => {
setTimeout(() => {
setFadingOutIssues((prev) => {
const next = new Set(prev);
next.delete(id);
return next;
});
}, 300);
},
});
if (!selectedCompanyId) { if (!selectedCompanyId) {
return <EmptyState icon={InboxIcon} message="Select a company to view inbox." />; return <EmptyState icon={InboxIcon} message="Select a company to view inbox." />;
} }
@@ -867,35 +892,53 @@ export function Inbox() {
My Recent Issues My Recent Issues
</h3> </h3>
<div className="divide-y divide-border border border-border"> <div className="divide-y divide-border border border-border">
{touchedIssues.map((issue) => ( {touchedIssues.map((issue) => {
<Link const isUnread = issue.isUnreadForMe && !fadingOutIssues.has(issue.id);
key={issue.id} const isFading = fadingOutIssues.has(issue.id);
to={`/issues/${issue.identifier ?? issue.id}`} return (
className="flex cursor-pointer items-center gap-3 px-4 py-3 transition-colors hover:bg-accent/50 no-underline text-inherit" <div
> key={issue.id}
<span className="flex w-4 shrink-0 justify-center"> className="flex items-center gap-3 px-4 py-3 transition-colors hover:bg-accent/50"
<span >
className={`h-2.5 w-2.5 rounded-full ${ <span className="flex w-4 shrink-0 justify-center">
issue.isUnreadForMe {(isUnread || isFading) && (
? "bg-blue-600 dark:bg-blue-400" <button
: "border border-muted-foreground/40 bg-transparent" type="button"
}`} onClick={(e) => {
aria-label={issue.isUnreadForMe ? "Unread" : "Read"} e.preventDefault();
/> e.stopPropagation();
</span> markReadMutation.mutate(issue.id);
<PriorityIcon priority={issue.priority} /> }}
<StatusIcon status={issue.status} /> className="group/dot flex h-4 w-4 items-center justify-center rounded-full transition-colors hover:bg-blue-500/20"
<span className="text-xs font-mono text-muted-foreground"> aria-label="Mark as read"
{issue.identifier ?? issue.id.slice(0, 8)} >
</span> <span
<span className="flex-1 truncate text-sm">{issue.title}</span> className={`h-2.5 w-2.5 rounded-full bg-blue-600 dark:bg-blue-400 transition-opacity duration-300 ${
<span className="shrink-0 text-xs text-muted-foreground"> isFading ? "opacity-0" : "opacity-100"
{issue.lastExternalCommentAt }`}
? `commented ${timeAgo(issue.lastExternalCommentAt)}` />
: `updated ${timeAgo(issue.updatedAt)}`} </button>
</span> )}
</Link> </span>
))} <Link
to={`/issues/${issue.identifier ?? issue.id}`}
className="flex flex-1 cursor-pointer items-center gap-3 no-underline text-inherit"
>
<PriorityIcon priority={issue.priority} />
<StatusIcon status={issue.status} />
<span className="text-xs font-mono text-muted-foreground">
{issue.identifier ?? issue.id.slice(0, 8)}
</span>
<span className="flex-1 truncate text-sm">{issue.title}</span>
<span className="shrink-0 text-xs text-muted-foreground">
{issue.lastExternalCommentAt
? `commented ${timeAgo(issue.lastExternalCommentAt)}`
: `updated ${timeAgo(issue.updatedAt)}`}
</span>
</Link>
</div>
);
})}
</div> </div>
</div> </div>
</> </>