57c29f62b4
- user_blocks table (composite PK), Block/Unblock button on profiles. Blocking severs any existing follow relationship both ways and prevents the blocked party from following, commenting on the blocker's recipes, or the blocker from seeing their comments. - reports table (recipe/comment/user targets, pending/reviewed/ dismissed status). ReportButton on comments, admin review queue at /admin/reports with dismiss/mark-reviewed actions, audit-logged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
55 lines
1.4 KiB
TypeScript
55 lines
1.4 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import { toast } from "sonner";
|
|
import { Ban } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
export function BlockButton({
|
|
targetUsername,
|
|
initialBlocked = false,
|
|
}: {
|
|
targetUsername: string;
|
|
initialBlocked?: boolean;
|
|
}) {
|
|
const router = useRouter();
|
|
const [blocked, setBlocked] = useState(initialBlocked);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
async function toggle() {
|
|
if (!blocked && !confirm(`Block @${targetUsername}? They won't be able to follow you or comment on your recipes.`)) {
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
try {
|
|
const res = await fetch(`/api/v1/users/${targetUsername}/block`, {
|
|
method: blocked ? "DELETE" : "POST",
|
|
});
|
|
if (!res.ok) {
|
|
const err = await res.json() as { error?: string };
|
|
toast.error(err.error ?? "Failed");
|
|
return;
|
|
}
|
|
setBlocked(!blocked);
|
|
toast.success(blocked ? "Unblocked" : "Blocked");
|
|
router.refresh();
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => { void toggle(); }}
|
|
disabled={loading}
|
|
className={blocked ? "" : "text-destructive hover:text-destructive"}
|
|
>
|
|
<Ban className="h-3.5 w-3.5" />
|
|
{loading ? "…" : blocked ? "Unblock" : "Block"}
|
|
</Button>
|
|
);
|
|
}
|