From 82c557e10ca787f18cd24a3f3bfcbf2021f89fea Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Tue, 9 Dec 2025 08:49:47 +0100 Subject: [PATCH] Add Event Management section to admin page, allowing users to manage events alongside preferences and user management. Updated UI to include event button and corresponding display area. --- app/admin/page.tsx | 22 +- app/api/admin/events/[id]/route.ts | 116 +++++++++ app/api/admin/events/route.ts | 82 +++++++ components/EventManagement.tsx | 381 +++++++++++++++++++++++++++++ 4 files changed, 600 insertions(+), 1 deletion(-) create mode 100644 app/api/admin/events/[id]/route.ts create mode 100644 app/api/admin/events/route.ts create mode 100644 components/EventManagement.tsx diff --git a/app/admin/page.tsx b/app/admin/page.tsx index 7276221..bc10569 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -6,6 +6,7 @@ import { useRouter } from "next/navigation"; import Navigation from "@/components/Navigation"; import ImageSelector from "@/components/ImageSelector"; import UserManagement from "@/components/UserManagement"; +import EventManagement from "@/components/EventManagement"; interface SitePreferences { id: string; @@ -14,7 +15,7 @@ interface SitePreferences { leaderboardBackground: string | null; } -type AdminSection = "preferences" | "users"; +type AdminSection = "preferences" | "users" | "events"; export default function AdminPage() { const { data: session, status } = useSession(); @@ -143,6 +144,16 @@ export default function AdminPage() { > Utilisateurs + {activeSection === "preferences" && ( @@ -245,6 +256,15 @@ export default function AdminPage() { )} + + {activeSection === "events" && ( +
+

+ Gestion des Événements +

+ +
+ )} diff --git a/app/api/admin/events/[id]/route.ts b/app/api/admin/events/[id]/route.ts new file mode 100644 index 0000000..b3fb9fb --- /dev/null +++ b/app/api/admin/events/[id]/route.ts @@ -0,0 +1,116 @@ +import { NextResponse } from "next/server"; +import { auth } from "@/lib/auth"; +import { prisma } from "@/lib/prisma"; +import { Role, EventType, EventStatus } from "@/prisma/generated/prisma/client"; + +export async function PUT( + request: Request, + { params }: { params: Promise<{ id: string }> } +) { + try { + const session = await auth(); + + if (!session?.user || session.user.role !== Role.ADMIN) { + return NextResponse.json({ error: "Accès refusé" }, { status: 403 }); + } + + const { id } = await params; + const body = await request.json(); + const { date, name, description, type, status } = body; + + // Vérifier que l'événement existe + const existingEvent = await prisma.event.findUnique({ + where: { id }, + }); + + if (!existingEvent) { + return NextResponse.json( + { error: "Événement non trouvé" }, + { status: 404 } + ); + } + + const updateData: { + date?: string; + name?: string; + description?: string; + type?: EventType; + status?: EventStatus; + } = {}; + + if (date !== undefined) updateData.date = date; + if (name !== undefined) updateData.name = name; + if (description !== undefined) updateData.description = description; + if (type !== undefined) { + if (!Object.values(EventType).includes(type)) { + return NextResponse.json( + { error: "Type d'événement invalide" }, + { status: 400 } + ); + } + updateData.type = type as EventType; + } + if (status !== undefined) { + if (!Object.values(EventStatus).includes(status)) { + return NextResponse.json( + { error: "Statut d'événement invalide" }, + { status: 400 } + ); + } + updateData.status = status as EventStatus; + } + + const event = await prisma.event.update({ + where: { id }, + data: updateData, + }); + + return NextResponse.json(event); + } catch (error) { + console.error("Error updating event:", error); + return NextResponse.json( + { error: "Erreur lors de la mise à jour de l'événement" }, + { status: 500 } + ); + } +} + +export async function DELETE( + request: Request, + { params }: { params: Promise<{ id: string }> } +) { + try { + const session = await auth(); + + if (!session?.user || session.user.role !== Role.ADMIN) { + return NextResponse.json({ error: "Accès refusé" }, { status: 403 }); + } + + const { id } = await params; + + // Vérifier que l'événement existe + const existingEvent = await prisma.event.findUnique({ + where: { id }, + }); + + if (!existingEvent) { + return NextResponse.json( + { error: "Événement non trouvé" }, + { status: 404 } + ); + } + + await prisma.event.delete({ + where: { id }, + }); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error("Error deleting event:", error); + return NextResponse.json( + { error: "Erreur lors de la suppression de l'événement" }, + { status: 500 } + ); + } +} + diff --git a/app/api/admin/events/route.ts b/app/api/admin/events/route.ts new file mode 100644 index 0000000..cab1a90 --- /dev/null +++ b/app/api/admin/events/route.ts @@ -0,0 +1,82 @@ +import { NextResponse } from "next/server"; +import { auth } from "@/lib/auth"; +import { prisma } from "@/lib/prisma"; +import { Role, EventType, EventStatus } from "@/prisma/generated/prisma/client"; + +export async function GET() { + try { + const session = await auth(); + + if (!session?.user || session.user.role !== Role.ADMIN) { + return NextResponse.json({ error: "Accès refusé" }, { status: 403 }); + } + + const events = await prisma.event.findMany({ + orderBy: { + date: "asc", + }, + }); + + return NextResponse.json(events); + } catch (error) { + console.error("Error fetching events:", error); + return NextResponse.json( + { error: "Erreur lors de la récupération des événements" }, + { status: 500 } + ); + } +} + +export async function POST(request: Request) { + try { + const session = await auth(); + + if (!session?.user || session.user.role !== Role.ADMIN) { + return NextResponse.json({ error: "Accès refusé" }, { status: 403 }); + } + + const body = await request.json(); + const { date, name, description, type, status } = body; + + if (!date || !name || !description || !type || !status) { + return NextResponse.json( + { error: "Tous les champs sont requis" }, + { status: 400 } + ); + } + + // Valider les enums + if (!Object.values(EventType).includes(type)) { + return NextResponse.json( + { error: "Type d'événement invalide" }, + { status: 400 } + ); + } + + if (!Object.values(EventStatus).includes(status)) { + return NextResponse.json( + { error: "Statut d'événement invalide" }, + { status: 400 } + ); + } + + const event = await prisma.event.create({ + data: { + date, + name, + description, + type: type as EventType, + status: status as EventStatus, + }, + }); + + return NextResponse.json(event); + } catch (error) { + console.error("Error creating event:", error); + return NextResponse.json( + { error: "Erreur lors de la création de l'événement" }, + { status: 500 } + ); + } +} + diff --git a/components/EventManagement.tsx b/components/EventManagement.tsx new file mode 100644 index 0000000..80265a7 --- /dev/null +++ b/components/EventManagement.tsx @@ -0,0 +1,381 @@ +"use client"; + +import { useState, useEffect } from "react"; + +interface Event { + id: string; + date: string; + name: string; + description: string; + type: "SUMMIT" | "LAUNCH" | "FESTIVAL" | "COMPETITION"; + status: "UPCOMING" | "LIVE" | "PAST"; + createdAt: string; + updatedAt: string; +} + +interface EventFormData { + date: string; + name: string; + description: string; + type: "SUMMIT" | "LAUNCH" | "FESTIVAL" | "COMPETITION"; + status: "UPCOMING" | "LIVE" | "PAST"; +} + +const eventTypes: Event["type"][] = [ + "SUMMIT", + "LAUNCH", + "FESTIVAL", + "COMPETITION", +]; +const eventStatuses: Event["status"][] = ["UPCOMING", "LIVE", "PAST"]; + +const getEventTypeLabel = (type: Event["type"]) => { + switch (type) { + case "SUMMIT": + return "Sommet"; + case "LAUNCH": + return "Lancement"; + case "FESTIVAL": + return "Festival"; + case "COMPETITION": + return "Compétition"; + default: + return type; + } +}; + +const getStatusLabel = (status: Event["status"]) => { + switch (status) { + case "UPCOMING": + return "À venir"; + case "LIVE": + return "En cours"; + case "PAST": + return "Passé"; + default: + return status; + } +}; + +export default function EventManagement() { + const [events, setEvents] = useState([]); + const [loading, setLoading] = useState(true); + const [editingEvent, setEditingEvent] = useState(null); + const [isCreating, setIsCreating] = useState(false); + const [saving, setSaving] = useState(false); + const [formData, setFormData] = useState({ + date: "", + name: "", + description: "", + type: "SUMMIT", + status: "UPCOMING", + }); + + useEffect(() => { + fetchEvents(); + }, []); + + const fetchEvents = async () => { + try { + const response = await fetch("/api/admin/events"); + if (response.ok) { + const data = await response.json(); + setEvents(data); + } + } catch (error) { + console.error("Error fetching events:", error); + } finally { + setLoading(false); + } + }; + + const handleCreate = () => { + setIsCreating(true); + setEditingEvent(null); + setFormData({ + date: "", + name: "", + description: "", + type: "SUMMIT", + status: "UPCOMING", + }); + }; + + const handleEdit = (event: Event) => { + setEditingEvent(event); + setIsCreating(false); + setFormData({ + date: event.date, + name: event.name, + description: event.description, + type: event.type, + status: event.status, + }); + }; + + const handleSave = async () => { + setSaving(true); + try { + let response; + if (isCreating) { + response = await fetch("/api/admin/events", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }); + } else if (editingEvent) { + response = await fetch(`/api/admin/events/${editingEvent.id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }); + } + + if (response?.ok) { + await fetchEvents(); + setEditingEvent(null); + setIsCreating(false); + setFormData({ + date: "", + name: "", + description: "", + type: "SUMMIT", + status: "UPCOMING", + }); + } else { + const error = await response?.json(); + alert(error.error || "Erreur lors de la sauvegarde"); + } + } catch (error) { + console.error("Error saving event:", error); + alert("Erreur lors de la sauvegarde"); + } finally { + setSaving(false); + } + }; + + const handleDelete = async (eventId: string) => { + if (!confirm("Êtes-vous sûr de vouloir supprimer cet événement ?")) { + return; + } + + try { + const response = await fetch(`/api/admin/events/${eventId}`, { + method: "DELETE", + }); + + if (response.ok) { + await fetchEvents(); + } else { + const error = await response.json(); + alert(error.error || "Erreur lors de la suppression"); + } + } catch (error) { + console.error("Error deleting event:", error); + alert("Erreur lors de la suppression"); + } + }; + + const handleCancel = () => { + setEditingEvent(null); + setIsCreating(false); + setFormData({ + date: "", + name: "", + description: "", + type: "SUMMIT", + status: "UPCOMING", + }); + }; + + if (loading) { + return
Chargement...
; + } + + return ( +
+
+

+ Événements ({events.length}) +

+ {!isCreating && !editingEvent && ( + + )} +
+ + {(isCreating || editingEvent) && ( +
+

+ {isCreating ? "Créer un événement" : "Modifier l'événement"} +

+
+
+ + + setFormData({ ...formData, date: e.target.value }) + } + className="w-full px-3 py-2 bg-black/60 border border-pixel-gold/30 rounded text-white text-sm" + /> +
+
+ + + setFormData({ ...formData, name: e.target.value }) + } + placeholder="Nom de l'événement" + className="w-full px-3 py-2 bg-black/60 border border-pixel-gold/30 rounded text-white text-sm" + /> +
+
+ +