Add secure admin panel for content moderation and contribution flagging

Adds an admin interface with authentication for manual content deletion and flagging. Implements a flagging system for user contributions and secures the admin panel with a secret token.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 923ae0e3-a363-4db8-b04a-e8baca2a1330
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 7e5834b1-796d-4a9e-bbde-cd91012292de
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8af7d2ec-2cc3-4ece-8af3-9f071488d072/923ae0e3-a363-4db8-b04a-e8baca2a1330/nghZcOj
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
pironantoine
2026-04-05 03:42:58 +00:00
parent e58c1cef85
commit 2a792cbbb5
5 changed files with 984 additions and 10 deletions
+41 -1
View File
@@ -27,7 +27,7 @@ import {
} from "@/components/ui/accordion";
import {
Loader2, PenTool, CheckCircle2, Info, AlertCircle, TrendingUp, Users, Scale,
Share2, Printer, Copy,
Share2, Printer, Copy, Flag,
} from "lucide-react";
import { useToast } from "@/hooks/use-toast";
@@ -71,6 +71,8 @@ const VALEURS = [
},
];
const API_BASE = import.meta.env.VITE_API_URL ?? "";
export default function Home() {
const queryClient = useQueryClient();
const { toast } = useToast();
@@ -79,6 +81,26 @@ export default function Home() {
message: string;
reason?: string;
} | null>(null);
const [flaggedIds, setFlaggedIds] = React.useState<Set<number>>(new Set());
const [flaggingId, setFlaggingId] = React.useState<number | null>(null);
const handleFlag = async (ideaId: number) => {
if (flaggedIds.has(ideaId) || flaggingId === ideaId) return;
setFlaggingId(ideaId);
try {
const res = await fetch(`${API_BASE}/api/ideas/${ideaId}/flag`, { method: "POST" });
if (res.ok) {
setFlaggedIds((prev) => new Set(prev).add(ideaId));
toast({ title: "Signalement envoyé", description: "Cette contribution a été signalée à l'administrateur." });
} else {
toast({ title: "Erreur", description: "Impossible d'envoyer le signalement.", variant: "destructive" });
}
} catch {
toast({ title: "Erreur réseau", description: "Vérifiez votre connexion.", variant: "destructive" });
} finally {
setFlaggingId(null);
}
};
const submitIdea = useSubmitIdea();
const { data: ideas, isLoading: isLoadingIdeas } = useListIdeas();
@@ -345,6 +367,24 @@ export default function Home() {
<span>
{format(new Date(idea.createdAt), "d MMM, HH:mm", { locale: fr })}
</span>
<button
onClick={() => handleFlag(idea.id)}
disabled={flaggedIds.has(idea.id) || flaggingId === idea.id}
className={`ml-auto flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity text-xs ${
flaggedIds.has(idea.id)
? "text-orange-500 opacity-100"
: "text-muted-foreground/50 hover:text-orange-500"
}`}
title={flaggedIds.has(idea.id) ? "Déjà signalé" : "Signaler cette contribution"}
aria-label="Signaler cette contribution"
>
{flaggingId === idea.id ? (
<Loader2 className="h-3 w-3 animate-spin" />
) : (
<Flag className="h-3 w-3" />
)}
{flaggedIds.has(idea.id) ? "Signalé" : "Signaler"}
</button>
</div>
</div>
))