Files
got-gaming/components/EventsPageSection.tsx

865 lines
32 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState, useEffect, useMemo, useRef } from "react";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import { calculateEventStatus } from "@/lib/eventStatus";
import FeedbackModal from "@/components/FeedbackModal";
interface Event {
id: string;
date: string | Date;
name: string;
description: string;
type: "ATELIER" | "KATA" | "PRESENTATION" | "LEARNING_HOUR";
room?: string | null;
time?: string | null;
maxPlaces?: number | null;
}
interface EventsPageSectionProps {
events: Event[];
backgroundImage: string;
initialRegistrations?: Record<string, boolean>;
}
const getEventTypeColor = (type: Event["type"]) => {
switch (type) {
case "ATELIER":
return "from-blue-600 to-cyan-500";
case "KATA":
return "from-yellow-600 to-amber-500";
case "PRESENTATION":
return "from-purple-600 to-pink-500";
case "LEARNING_HOUR":
return "from-green-600 to-emerald-500";
default:
return "from-gray-600 to-gray-500";
}
};
const getEventTypeLabel = (type: Event["type"]) => {
switch (type) {
case "ATELIER":
return "Atelier";
case "KATA":
return "Kata";
case "PRESENTATION":
return "Présentation";
case "LEARNING_HOUR":
return "Learning Hour";
default:
return type;
}
};
const getStatusBadge = (status: "UPCOMING" | "LIVE" | "PAST") => {
switch (status) {
case "UPCOMING":
return (
<span className="px-3 py-1 bg-green-900/50 border border-green-500/50 text-green-400 text-xs uppercase tracking-widest rounded">
À venir
</span>
);
case "LIVE":
return (
<span className="px-3 py-1 bg-red-900/50 border border-red-500/50 text-red-400 text-xs uppercase tracking-widest rounded animate-pulse">
En direct
</span>
);
case "PAST":
return (
<span className="px-3 py-1 bg-gray-800/50 border border-gray-600/50 text-gray-400 text-xs uppercase tracking-widest rounded">
Passé
</span>
);
}
};
export default function EventsPageSection({
events,
backgroundImage,
initialRegistrations = {},
}: EventsPageSectionProps) {
const { data: session } = useSession();
const router = useRouter();
const [registrations, setRegistrations] =
useState<Record<string, boolean>>(initialRegistrations);
const [loading, setLoading] = useState<Record<string, boolean>>({});
const [error, setError] = useState<string>("");
const [currentMonth, setCurrentMonth] = useState(new Date());
const [selectedEvent, setSelectedEvent] = useState<Event | null>(null);
const [feedbackEventId, setFeedbackEventId] = useState<string | null>(null);
// Helper function pour obtenir le statut d'un événement
const getEventStatus = (event: Event) => calculateEventStatus(event.date);
// Déterminer si on a des données initiales valides
const hasInitialData = useMemo(
() => Object.keys(initialRegistrations).length > 0,
[initialRegistrations]
);
// Ref pour tracker si on a déjà utilisé les données initiales
const hasUsedInitialData = useRef(hasInitialData);
// Séparer et trier les événements (du plus récent au plus ancien)
// Le statut est calculé automatiquement en fonction de la date
const upcomingEvents = events
.filter((e) => {
const status = calculateEventStatus(e.date);
return status === "UPCOMING" || status === "LIVE";
})
.sort((a, b) => {
// Trier par date décroissante (du plus récent au plus ancien)
const dateA = typeof a.date === "string" ? new Date(a.date) : a.date;
const dateB = typeof b.date === "string" ? new Date(b.date) : b.date;
return dateB.getTime() - dateA.getTime();
});
const pastEvents = events
.filter((e) => calculateEventStatus(e.date) === "PAST")
.sort((a, b) => {
// Trier par date décroissante (du plus récent au plus ancien)
const dateA = typeof a.date === "string" ? new Date(a.date) : a.date;
const dateB = typeof b.date === "string" ? new Date(b.date) : b.date;
return dateB.getTime() - dateA.getTime();
});
// Créer un map des événements par date pour le calendrier
const eventsByDate: Record<string, Event[]> = {};
events.forEach((event) => {
// Convertir la date en string YYYY-MM-DD pour le calendrier
let eventDate: Date;
if (typeof event.date === "string") {
eventDate = new Date(event.date);
} else if (event.date instanceof Date) {
eventDate = event.date;
} else {
// Fallback si c'est déjà un objet Date
eventDate = new Date(event.date);
}
// Utiliser UTC pour éviter les problèmes de fuseau horaire
const year = eventDate.getUTCFullYear();
const month = String(eventDate.getUTCMonth() + 1).padStart(2, "0");
const day = String(eventDate.getUTCDate()).padStart(2, "0");
const dateKey = `${year}-${month}-${day}`; // YYYY-MM-DD
if (!eventsByDate[dateKey]) {
eventsByDate[dateKey] = [];
}
eventsByDate[dateKey].push(event);
});
// Mettre à jour le ref quand on a des données initiales
useEffect(() => {
if (hasInitialData) {
hasUsedInitialData.current = true;
}
}, [hasInitialData]);
// Ne charger depuis l'API que si on n'a pas de données initiales
// (cas où l'utilisateur se connecte après le chargement de la page)
useEffect(() => {
// Si on a déjà des données initiales, ne jamais charger depuis l'API
if (hasUsedInitialData.current) {
return;
}
// Si pas de session, ne rien faire (on garde les données vides)
if (!session?.user?.id) {
return;
}
// Charger les inscriptions depuis l'API seulement si on n'a pas de données initiales
// On charge pour tous les événements (passés et à venir) pour permettre le feedback
const checkRegistrations = async () => {
const registrationChecks = events.map(async (event) => {
try {
const response = await fetch(`/api/events/${event.id}/register`);
const data = await response.json();
return { eventId: event.id, registered: data.registered || false };
} catch {
return { eventId: event.id, registered: false };
}
});
const results = await Promise.all(registrationChecks);
const registrationsMap: Record<string, boolean> = {};
results.forEach(({ eventId, registered }) => {
registrationsMap[eventId] = registered;
});
setRegistrations(registrationsMap);
};
checkRegistrations();
}, [session?.user?.id, events]);
// Fonctions pour le calendrier
const getDaysInMonth = (date: Date) => {
// Utiliser UTC pour correspondre au format des événements
const year = date.getUTCFullYear();
const month = date.getUTCMonth();
return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
};
const getFirstDayOfMonth = (date: Date) => {
// Utiliser UTC pour correspondre au format des événements
// getUTCDay() retourne 0 (dimanche) à 6 (samedi)
// On convertit pour que lundi = 0, mardi = 1, ..., dimanche = 6
const year = date.getUTCFullYear();
const month = date.getUTCMonth();
const dayOfWeek = new Date(Date.UTC(year, month, 1)).getUTCDay();
// Convertir : dimanche (0) -> 6, lundi (1) -> 0, mardi (2) -> 1, etc.
return (dayOfWeek + 6) % 7;
};
const formatMonthYear = (date: Date) => {
return date.toLocaleDateString("fr-FR", {
month: "long",
year: "numeric",
});
};
const renderCalendar = () => {
const daysInMonth = getDaysInMonth(currentMonth);
const firstDay = getFirstDayOfMonth(currentMonth);
const days: (number | null)[] = [];
// Ajouter des jours vides pour le début du mois
for (let i = 0; i < firstDay; i++) {
days.push(null);
}
// Ajouter les jours du mois
for (let day = 1; day <= daysInMonth; day++) {
days.push(day);
}
// Utiliser UTC pour correspondre au format des événements
const year = currentMonth.getUTCFullYear();
const month = currentMonth.getUTCMonth() + 1;
return (
<div className="bg-black/60 border border-pixel-gold/30 rounded-lg p-4 backdrop-blur-sm max-w-2xl mx-auto">
{/* Header du calendrier */}
<div className="flex items-center justify-between mb-3">
<button
onClick={() => {
const year = currentMonth.getUTCFullYear();
const month = currentMonth.getUTCMonth();
setCurrentMonth(new Date(Date.UTC(year, month - 1, 1)));
}}
className="px-2 py-1 border border-pixel-gold/50 text-pixel-gold hover:bg-pixel-gold/10 rounded transition text-sm"
>
</button>
<h3 className="text-base font-bold text-white uppercase tracking-widest">
{formatMonthYear(currentMonth)}
</h3>
<button
onClick={() => {
const year = currentMonth.getUTCFullYear();
const month = currentMonth.getUTCMonth();
setCurrentMonth(new Date(Date.UTC(year, month + 1, 1)));
}}
className="px-2 py-1 border border-pixel-gold/50 text-pixel-gold hover:bg-pixel-gold/10 rounded transition text-sm"
>
</button>
</div>
{/* Jours de la semaine */}
<div className="grid grid-cols-7 gap-0.5 mb-1">
{["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"].map((day) => (
<div
key={day}
className="text-center text-[10px] text-gray-400 font-semibold py-1"
>
{day}
</div>
))}
</div>
{/* Grille du calendrier */}
<div className="grid grid-cols-7 gap-0.5">
{days.map((day, index) => {
if (day === null) {
return <div key={index} className="aspect-square"></div>;
}
const dateKey = `${year}-${String(month).padStart(2, "0")}-${String(
day
).padStart(2, "0")}`;
const dayEvents = eventsByDate[dateKey] || [];
// Vérifier si c'est aujourd'hui en utilisant UTC
const today = new Date();
const todayKey = `${today.getUTCFullYear()}-${String(
today.getUTCMonth() + 1
).padStart(2, "0")}-${String(today.getUTCDate()).padStart(2, "0")}`;
const isToday = todayKey === dateKey;
const hasEvents = dayEvents.length > 0;
// Déterminer la couleur principale selon le type d'événement
const hasUpcoming = dayEvents.some(
(e) => getEventStatus(e) === "UPCOMING"
);
const hasLive = dayEvents.some((e) => getEventStatus(e) === "LIVE");
const hasPast = dayEvents.some((e) => getEventStatus(e) === "PAST");
let eventBorderColor = "";
let eventBgColor = "";
if (hasLive) {
eventBorderColor = "border-red-500/80";
eventBgColor = "bg-red-500/20";
} else if (hasUpcoming) {
eventBorderColor = "border-green-500/80";
eventBgColor = "bg-green-500/20";
} else if (hasPast) {
eventBorderColor = "border-gray-500/60";
eventBgColor = "bg-gray-500/15";
}
return (
<div
key={index}
onClick={() => {
if (hasEvents && dayEvents.length > 0) {
setSelectedEvent(dayEvents[0]);
}
}}
className={`aspect-square border rounded flex flex-col items-center justify-center relative ${
isToday
? "bg-pixel-gold/30 border-pixel-gold/70 border-2"
: hasEvents
? `${eventBgColor} ${eventBorderColor} border-2`
: "border-pixel-gold/10"
} ${
hasEvents ? "ring-2 ring-offset-1 ring-offset-black" : ""
} ${
hasLive
? "ring-red-500/50"
: hasUpcoming
? "ring-green-500/50"
: ""
} ${
hasEvents
? "cursor-pointer hover:opacity-80 transition-opacity"
: ""
}`}
>
<div
className={`text-[11px] font-bold ${
isToday
? "text-pixel-gold"
: hasEvents
? "text-white"
: "text-gray-400"
}`}
>
{day}
</div>
{hasEvents && (
<div className="absolute bottom-0.5 left-0 right-0 flex justify-center gap-0.5">
{dayEvents.slice(0, 3).map((event) => {
const status = getEventStatus(event);
return (
<div
key={event.id}
className={`w-1 h-1 rounded-full ${
status === "UPCOMING"
? "bg-green-400"
: status === "LIVE"
? "bg-red-400 animate-pulse"
: "bg-gray-400"
}`}
title={event.name}
/>
);
})}
{dayEvents.length > 3 && (
<div className="w-1 h-1 rounded-full bg-gray-400" />
)}
</div>
)}
</div>
);
})}
</div>
</div>
);
};
const truncateDescription = (text: string, maxLength: number = 150) => {
if (text.length <= maxLength) return text;
return text.substring(0, maxLength).trim() + "...";
};
const renderEventCard = (event: Event) => (
<div
key={event.id}
onClick={() => setSelectedEvent(event)}
className="bg-black/60 border border-pixel-gold/30 rounded-lg overflow-hidden backdrop-blur-sm hover:border-pixel-gold/50 transition group cursor-pointer"
>
{/* Event Header */}
<div
className={`h-2 bg-gradient-to-r ${getEventTypeColor(event.type)}`}
></div>
{/* Event Content */}
<div className="p-6">
{/* Status Badge */}
<div className="flex justify-between items-start mb-4">
{getStatusBadge(getEventStatus(event))}
<span className="text-pixel-gold text-xs uppercase tracking-widest">
{getEventTypeLabel(event.type)}
</span>
</div>
{/* Date */}
<div className="text-white text-sm font-bold uppercase tracking-widest mb-3">
{typeof event.date === "string"
? new Date(event.date).toLocaleDateString("fr-FR", {
day: "numeric",
month: "long",
year: "numeric",
})
: event.date.toLocaleDateString("fr-FR", {
day: "numeric",
month: "long",
year: "numeric",
})}
</div>
{/* Event Name */}
<h3 className="text-xl font-bold text-white mb-3 group-hover:text-pixel-gold transition">
{event.name}
</h3>
{/* Event Details */}
{(event.room || event.time || event.maxPlaces) && (
<div className="flex flex-wrap gap-3 mb-4 text-sm">
{event.room && (
<div className="flex items-center gap-1.5 text-gray-300">
<span className="text-pixel-gold">📍</span>
<span className="font-semibold">Salle:</span>
<span>{event.room}</span>
</div>
)}
{event.time && (
<div className="flex items-center gap-1.5 text-gray-300">
<span className="text-pixel-gold">🕐</span>
<span className="font-semibold">Heure:</span>
<span>{event.time}</span>
</div>
)}
{event.maxPlaces && (
<div className="flex items-center gap-1.5 text-gray-300">
<span className="text-pixel-gold">👥</span>
<span className="font-semibold">Places:</span>
<span>{event.maxPlaces}</span>
</div>
)}
</div>
)}
{/* Description (truncated) */}
<p className="text-gray-400 text-sm leading-relaxed mb-4">
{truncateDescription(event.description)}
</p>
{event.description.length > 150 && (
<p className="text-pixel-gold text-xs mb-4 italic">
Cliquez pour voir plus...
</p>
)}
{/* Action Button */}
{getEventStatus(event) === "UPCOMING" && (
<>
{registrations[event.id] ? (
<button
onClick={(e) => {
e.stopPropagation();
handleUnregister(event.id);
}}
disabled={loading[event.id]}
className="w-full px-4 py-2 border border-green-500/50 bg-green-900/20 text-green-400 uppercase text-xs tracking-widest rounded hover:bg-green-900/30 transition disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading[event.id] ? "Annulation..." : "Inscrit ✓"}
</button>
) : (
<button
onClick={(e) => {
e.stopPropagation();
handleRegister(event.id);
}}
disabled={loading[event.id]}
className="w-full px-4 py-2 border border-pixel-gold/50 bg-black/40 text-white uppercase text-xs tracking-widest rounded hover:bg-pixel-gold/10 hover:border-pixel-gold transition disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading[event.id] ? "Inscription..." : "S'inscrire maintenant"}
</button>
)}
</>
)}
{getEventStatus(event) === "LIVE" && (
<button className="w-full px-4 py-2 border border-red-500/50 bg-red-900/20 text-red-400 uppercase text-xs tracking-widest rounded hover:bg-red-900/30 transition animate-pulse">
Rejoindre en direct
</button>
)}
{getEventStatus(event) === "PAST" && (
<button
onClick={(e) => {
e.stopPropagation();
if (!session?.user?.id) {
router.push("/login");
return;
}
setFeedbackEventId(event.id);
}}
className="w-full px-4 py-2 border border-pixel-gold/50 bg-black/40 text-white uppercase text-xs tracking-widest rounded hover:bg-pixel-gold/10 hover:border-pixel-gold transition"
>
Donner un feedback
</button>
)}
</div>
</div>
);
const handleRegister = async (eventId: string) => {
if (!session?.user?.id) {
router.push("/login");
return;
}
setLoading((prev) => ({ ...prev, [eventId]: true }));
setError("");
try {
const response = await fetch(`/api/events/${eventId}/register`, {
method: "POST",
});
const data = await response.json();
if (!response.ok) {
setError(data.error || "Une erreur est survenue");
return;
}
setRegistrations((prev) => ({
...prev,
[eventId]: true,
}));
} catch {
setError("Une erreur est survenue");
} finally {
setLoading((prev) => ({ ...prev, [eventId]: false }));
}
};
const handleUnregister = async (eventId: string) => {
setLoading((prev) => ({ ...prev, [eventId]: true }));
setError("");
try {
const response = await fetch(`/api/events/${eventId}/register`, {
method: "DELETE",
});
if (!response.ok) {
const data = await response.json();
setError(data.error || "Une erreur est survenue");
return;
}
setRegistrations((prev) => ({
...prev,
[eventId]: false,
}));
} catch {
setError("Une erreur est survenue");
} finally {
setLoading((prev) => ({ ...prev, [eventId]: false }));
}
};
return (
<section className="relative w-full min-h-screen flex flex-col items-center justify-center overflow-hidden pt-24 pb-16">
{/* Background Image */}
<div
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
style={{
backgroundImage: `url('${backgroundImage}')`,
}}
>
{/* Dark overlay for readability */}
<div className="absolute inset-0 bg-gradient-to-b from-black/70 via-black/60 to-black/80"></div>
</div>
{/* Content */}
<div className="relative z-10 w-full max-w-6xl mx-auto px-8 py-16">
{/* Title Section */}
<div className="text-center mb-16">
<h1 className="text-5xl md:text-7xl font-gaming font-black mb-4 tracking-tight">
<span
className="bg-gradient-to-r from-pixel-gold via-orange-400 to-pixel-gold bg-clip-text text-transparent"
style={{
textShadow: "0 0 30px rgba(218, 165, 32, 0.5)",
}}
>
EVENTS
</span>
</h1>
<div className="text-pixel-gold text-lg md:text-xl font-gaming-subtitle font-semibold flex items-center justify-center gap-2 mb-6 tracking-wide">
<span></span>
<span>Événements à venir et passés</span>
<span></span>
</div>
<p className="text-gray-400 text-sm max-w-2xl mx-auto">
Rejoignez-nous pour des événements tech passionnants, des
compétitions et des célébrations tout au long de l&apos;année
</p>
</div>
{/* Événements à venir */}
{upcomingEvents.length > 0 && (
<div className="mb-16">
<h2 className="text-3xl font-bold text-white mb-8 text-center uppercase tracking-widest">
<span className="bg-gradient-to-r from-green-400 to-green-600 bg-clip-text text-transparent">
Événements à venir
</span>
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{upcomingEvents.map(renderEventCard)}
</div>
</div>
)}
{/* Calendrier */}
<div className="mb-16">
<h2 className="text-2xl font-bold text-white mb-6 text-center uppercase tracking-widest">
<span className="bg-gradient-to-r from-pixel-gold via-orange-400 to-pixel-gold bg-clip-text text-transparent">
Calendrier
</span>
</h2>
{renderCalendar()}
</div>
{/* Événements passés */}
{pastEvents.length > 0 && (
<div className="mb-16">
<h2 className="text-3xl font-bold text-white mb-8 text-center uppercase tracking-widest">
<span className="bg-gradient-to-r from-gray-400 to-gray-600 bg-clip-text text-transparent">
Événements passés
</span>
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{pastEvents.map(renderEventCard)}
</div>
</div>
)}
{/* Error Message */}
{error && (
<div className="mt-6 text-center">
<p className="text-red-400 text-sm">{error}</p>
</div>
)}
{/* Footer Info */}
{/* <div className="mt-12 text-center">
<p className="text-gray-500 text-sm">
Restez informé de nos derniers événements et annonces
</p>
</div> */}
</div>
{/* Event Modal */}
{selectedEvent && (
<div
className="fixed inset-0 z-[200] flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm"
onClick={() => setSelectedEvent(null)}
>
<div
className="bg-black border-2 border-pixel-gold/70 rounded-lg max-w-3xl w-full max-h-[90vh] overflow-y-auto shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
<div className="p-8">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
{getStatusBadge(
selectedEvent ? getEventStatus(selectedEvent) : "UPCOMING"
)}
<span className="px-3 py-1 bg-pixel-gold/20 border border-pixel-gold/50 text-pixel-gold text-xs uppercase rounded">
{getEventTypeLabel(selectedEvent.type)}
</span>
</div>
<h2 className="text-3xl font-bold text-white uppercase tracking-wide">
{selectedEvent.name}
</h2>
</div>
<button
onClick={() => setSelectedEvent(null)}
className="text-gray-400 hover:text-pixel-gold text-3xl font-bold transition ml-4"
>
×
</button>
</div>
{/* Event Header Color Bar */}
<div
className={`h-1 bg-gradient-to-r ${getEventTypeColor(
selectedEvent.type
)} mb-6 rounded`}
></div>
{/* Date */}
<div className="text-white text-lg font-bold uppercase tracking-widest mb-4">
{typeof selectedEvent.date === "string"
? new Date(selectedEvent.date).toLocaleDateString("fr-FR", {
day: "numeric",
month: "long",
year: "numeric",
})
: selectedEvent.date.toLocaleDateString("fr-FR", {
day: "numeric",
month: "long",
year: "numeric",
})}
</div>
{/* Event Details */}
{(selectedEvent.room ||
selectedEvent.time ||
selectedEvent.maxPlaces) && (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
{selectedEvent.room && (
<div className="flex items-center gap-2 text-gray-300 bg-black/40 p-3 rounded border border-pixel-gold/20">
<span className="text-pixel-gold text-xl">📍</span>
<div>
<div className="text-xs text-gray-400 uppercase tracking-wider">
Salle
</div>
<div className="font-semibold">
{selectedEvent.room}
</div>
</div>
</div>
)}
{selectedEvent.time && (
<div className="flex items-center gap-2 text-gray-300 bg-black/40 p-3 rounded border border-pixel-gold/20">
<span className="text-pixel-gold text-xl">🕐</span>
<div>
<div className="text-xs text-gray-400 uppercase tracking-wider">
Heure
</div>
<div className="font-semibold">
{selectedEvent.time}
</div>
</div>
</div>
)}
{selectedEvent.maxPlaces && (
<div className="flex items-center gap-2 text-gray-300 bg-black/40 p-3 rounded border border-pixel-gold/20">
<span className="text-pixel-gold text-xl">👥</span>
<div>
<div className="text-xs text-gray-400 uppercase tracking-wider">
Places
</div>
<div className="font-semibold">
{selectedEvent.maxPlaces}
</div>
</div>
</div>
)}
</div>
)}
{/* Full Description */}
<div className="mb-6">
<h3 className="text-pixel-gold text-sm uppercase tracking-widest mb-3">
Description
</h3>
<p className="text-gray-300 text-sm leading-relaxed whitespace-pre-line">
{selectedEvent.description}
</p>
</div>
{/* Action Button */}
{selectedEvent &&
getEventStatus(selectedEvent) === "UPCOMING" && (
<div className="pt-4 border-t border-pixel-gold/20">
{registrations[selectedEvent.id] ? (
<button
onClick={(e) => {
e.stopPropagation();
handleUnregister(selectedEvent.id);
setSelectedEvent(null);
}}
disabled={loading[selectedEvent.id]}
className="w-full px-4 py-3 border border-green-500/50 bg-green-900/20 text-green-400 uppercase text-sm tracking-widest rounded hover:bg-green-900/30 transition disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading[selectedEvent.id]
? "Annulation..."
: "Se désinscrire"}
</button>
) : (
<button
onClick={(e) => {
e.stopPropagation();
handleRegister(selectedEvent.id);
setSelectedEvent(null);
}}
disabled={loading[selectedEvent.id]}
className="w-full px-4 py-3 border border-pixel-gold/50 bg-pixel-gold/10 text-white uppercase text-sm tracking-widest rounded hover:bg-pixel-gold/20 hover:border-pixel-gold transition disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading[selectedEvent.id]
? "Inscription..."
: "S'inscrire maintenant"}
</button>
)}
</div>
)}
{selectedEvent && getEventStatus(selectedEvent) === "LIVE" && (
<div className="pt-4 border-t border-pixel-gold/20">
<button className="w-full px-4 py-3 border border-red-500/50 bg-red-900/20 text-red-400 uppercase text-sm tracking-widest rounded hover:bg-red-900/30 transition animate-pulse">
Rejoindre en direct
</button>
</div>
)}
{selectedEvent && getEventStatus(selectedEvent) === "PAST" && (
<div className="pt-4 border-t border-pixel-gold/20">
<button
onClick={(e) => {
e.stopPropagation();
if (!session?.user?.id) {
router.push("/login");
setSelectedEvent(null);
return;
}
setFeedbackEventId(selectedEvent.id);
setSelectedEvent(null);
}}
className="w-full px-4 py-3 border border-pixel-gold/50 bg-black/40 text-white uppercase text-sm tracking-widest rounded hover:bg-pixel-gold/10 hover:border-pixel-gold transition"
>
Donner un feedback
</button>
</div>
)}
</div>
</div>
</div>
)}
{/* Feedback Modal */}
<FeedbackModal
eventId={feedbackEventId}
onClose={() => setFeedbackEventId(null)}
/>
</section>
);
}