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:
@@ -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>
|
||||
))
|
||||
|
||||
Reference in New Issue
Block a user