feat: direct messages
1:1 conversations (userAId < userBId dedup pair), messages, per-participant read tracking (conversation_reads). Block relationship is enforced on every send. UI: /messages list + /messages/[id] thread (5s poll), MessageButton on profiles, unread-badged nav icon. This completes the social-feature backlog: notifications, rate limiting, blocking, reporting, search/discovery, mentions, DMs, plus fixes for the recipe-visibility 404, follow race, and 2-level comment thread cap found during the earlier audit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { MessageCircle } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
export function MessagesNavLink() {
|
||||
const [unreadTotal, setUnreadTotal] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
async function load() {
|
||||
try {
|
||||
const res = await fetch("/api/v1/conversations");
|
||||
if (!res.ok || cancelled) return;
|
||||
const data = (await res.json()) as { conversations: { unreadCount: number }[] };
|
||||
setUnreadTotal(data.conversations.reduce((sum, c) => sum + c.unreadCount, 0));
|
||||
} catch {
|
||||
// silent
|
||||
}
|
||||
}
|
||||
void load();
|
||||
const interval = setInterval(load, 30_000);
|
||||
return () => { cancelled = true; clearInterval(interval); };
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Button variant="ghost" size="icon" className="relative" nativeButton={false} render={<Link href="/messages" />}>
|
||||
<MessageCircle className="h-4 w-4" />
|
||||
{unreadTotal > 0 && (
|
||||
<Badge
|
||||
variant="destructive"
|
||||
className="absolute -top-1 -right-1 h-4 min-w-4 px-1 text-[10px] leading-none flex items-center justify-center"
|
||||
>
|
||||
{unreadTotal > 9 ? "9+" : unreadTotal}
|
||||
</Badge>
|
||||
)}
|
||||
<span className="sr-only">Messages</span>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user