Files
Epicure/apps/web/components/social/message-button.tsx
T
Arnaud c3776238c7 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>
2026-07-03 22:24:56 +02:00

40 lines
1.2 KiB
TypeScript

"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { MessageCircle } from "lucide-react";
import { Button } from "@/components/ui/button";
export function MessageButton({ targetUsername }: { targetUsername: string }) {
const router = useRouter();
const [loading, setLoading] = useState(false);
async function startConversation() {
setLoading(true);
try {
const res = await fetch("/api/v1/conversations", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username: targetUsername }),
});
if (!res.ok) {
const err = await res.json() as { error?: string };
toast.error(err.error ?? "Failed to start conversation");
return;
}
const { conversationId } = await res.json() as { conversationId: string };
router.push(`/messages/${conversationId}`);
} finally {
setLoading(false);
}
}
return (
<Button variant="outline" size="sm" onClick={() => { void startConversation(); }} disabled={loading}>
<MessageCircle className="h-3.5 w-3.5" />
Message
</Button>
);
}