Add feedback functionality to EventsPageSection: Integrate FeedbackModal for event feedback submission, allowing users to provide feedback on selected events. Update event handling to manage feedback state and improve user interaction.

This commit is contained in:
Julien Froidefond
2025-12-10 12:06:30 +01:00
parent 80e9d953ae
commit 9fde18a35e
3 changed files with 311 additions and 3 deletions

5
app/feedback/layout.tsx Normal file
View File

@@ -0,0 +1,5 @@
import type { ReactNode } from "react";
export default function FeedbackLayout({ children }: { children: ReactNode }) {
return <>{children}</>;
}

View File

@@ -4,6 +4,7 @@ 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;
@@ -88,6 +89,7 @@ export default function EventsPageSection({
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);
@@ -500,7 +502,7 @@ export default function EventsPageSection({
<button
onClick={(e) => {
e.stopPropagation();
router.push(`/feedback/${event.id}`);
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"
>
@@ -817,8 +819,15 @@ export default function EventsPageSection({
)}
{selectedEvent && getEventStatus(selectedEvent) === "PAST" && (
<div className="pt-4 border-t border-pixel-gold/20">
<button className="w-full px-4 py-3 border border-gray-600/50 bg-gray-900/20 text-gray-500 uppercase text-sm tracking-widest rounded cursor-not-allowed">
Événement terminé
<button
onClick={(e) => {
e.stopPropagation();
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>
)}
@@ -826,6 +835,12 @@ export default function EventsPageSection({
</div>
</div>
)}
{/* Feedback Modal */}
<FeedbackModal
eventId={feedbackEventId}
onClose={() => setFeedbackEventId(null)}
/>
</section>
);
}

View File

@@ -0,0 +1,288 @@
"use client";
import { useState, useEffect, type FormEvent } from "react";
import { useSession } from "next-auth/react";
interface Event {
id: string;
name: string;
date: string;
description: string;
}
interface Feedback {
id: string;
rating: number;
comment: string | null;
}
interface FeedbackModalProps {
eventId: string | null;
eventName?: string;
onClose: () => void;
}
export default function FeedbackModal({
eventId,
eventName,
onClose,
}: FeedbackModalProps) {
const { status } = useSession();
const [event, setEvent] = useState<Event | null>(null);
const [existingFeedback, setExistingFeedback] = useState<Feedback | null>(
null
);
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState("");
const [success, setSuccess] = useState(false);
const [rating, setRating] = useState(0);
const [comment, setComment] = useState("");
// Réinitialiser les états quand eventId change
useEffect(() => {
if (eventId) {
setEvent(null);
setExistingFeedback(null);
setRating(0);
setComment("");
setError("");
setSuccess(false);
setLoading(true);
}
}, [eventId]);
const fetchEventAndFeedback = async () => {
if (!eventId) return;
try {
// Récupérer l'événement
const eventResponse = await fetch(`/api/events/${eventId}`);
if (!eventResponse.ok) {
setError("Événement introuvable");
setLoading(false);
return;
}
const eventData = await eventResponse.json();
setEvent(eventData);
// Récupérer le feedback existant si disponible
const feedbackResponse = await fetch(`/api/feedback/${eventId}`);
if (feedbackResponse.ok) {
const feedbackData = await feedbackResponse.json();
if (feedbackData.feedback) {
setExistingFeedback(feedbackData.feedback);
setRating(feedbackData.feedback.rating);
setComment(feedbackData.feedback.comment || "");
} else {
// Pas de feedback existant, réinitialiser
setRating(0);
setComment("");
}
} else {
// Pas de feedback existant, réinitialiser
setRating(0);
setComment("");
}
} catch {
setError("Erreur lors du chargement des données");
} finally {
setLoading(false);
}
};
useEffect(() => {
if (status === "unauthenticated") {
onClose();
return;
}
if (status === "authenticated" && eventId) {
fetchEventAndFeedback();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [status, eventId]);
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
if (!eventId) return;
setError("");
setSuccess(false);
if (rating === 0) {
setError("Veuillez sélectionner une note");
return;
}
setSubmitting(true);
try {
const response = await fetch(`/api/feedback/${eventId}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
rating,
comment: comment.trim() || null,
}),
});
const data = await response.json();
if (!response.ok) {
setError(data.error || "Erreur lors de l'enregistrement");
return;
}
setSuccess(true);
setExistingFeedback(data.feedback);
// Fermer la modale après 1.5 secondes
setTimeout(() => {
onClose();
}, 1500);
} catch {
setError("Erreur lors de l'enregistrement");
} finally {
setSubmitting(false);
}
};
const handleClose = () => {
if (!submitting) {
onClose();
}
};
if (!eventId) return null;
return (
<div
className="fixed inset-0 z-[200] flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm"
onClick={handleClose}
>
<div
className="bg-black border-2 border-pixel-gold/70 rounded-lg max-w-2xl 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">
<h1 className="text-4xl font-gaming font-black">
<span className="bg-gradient-to-r from-pixel-gold via-orange-400 to-pixel-gold bg-clip-text text-transparent">
FEEDBACK
</span>
</h1>
<button
onClick={handleClose}
disabled={submitting}
className="text-gray-400 hover:text-pixel-gold text-3xl font-bold transition disabled:opacity-50 disabled:cursor-not-allowed"
>
×
</button>
</div>
{loading ? (
<div className="text-white text-center py-8">Chargement...</div>
) : !event ? (
<div className="text-red-400 text-center py-8">
Événement introuvable
</div>
) : (
<>
<p className="text-gray-400 text-sm text-center mb-2">
{existingFeedback
? "Modifier votre feedback pour"
: "Donnez votre avis sur"}
</p>
<p className="text-pixel-gold text-lg font-semibold text-center mb-8">
{event.name}
</p>
{success && (
<div className="bg-green-900/50 border border-green-500/50 text-green-400 px-4 py-3 rounded text-sm mb-6">
Feedback enregistré avec succès !
</div>
)}
{error && (
<div className="bg-red-900/50 border border-red-500/50 text-red-400 px-4 py-3 rounded text-sm mb-6">
{error}
</div>
)}
<form onSubmit={handleSubmit} className="space-y-6">
{/* Rating */}
<div>
<label className="block text-sm font-semibold text-gray-300 mb-4 uppercase tracking-wider">
Note
</label>
<div className="flex items-center justify-center gap-2">
{[1, 2, 3, 4, 5].map((star) => (
<button
key={star}
type="button"
onClick={() => setRating(star)}
disabled={submitting}
className={`text-4xl transition-transform hover:scale-110 disabled:hover:scale-100 ${
star <= rating
? "text-pixel-gold"
: "text-gray-600 hover:text-gray-500"
}`}
aria-label={`Noter ${star} étoile${star > 1 ? "s" : ""}`}
>
</button>
))}
</div>
<p className="text-gray-500 text-xs text-center mt-2">
{rating > 0 && `${rating}/5`}
</p>
</div>
{/* Comment */}
<div>
<label
htmlFor="comment"
className="block text-sm font-semibold text-gray-300 mb-2 uppercase tracking-wider"
>
Commentaire (optionnel)
</label>
<textarea
id="comment"
value={comment}
onChange={(e) => setComment(e.target.value)}
rows={6}
maxLength={1000}
disabled={submitting}
className="w-full px-4 py-3 bg-black/60 border border-pixel-gold/30 rounded text-white placeholder-gray-500 focus:outline-none focus:border-pixel-gold transition resize-none disabled:opacity-50"
placeholder="Partagez votre expérience, vos suggestions..."
/>
<p className="text-gray-500 text-xs mt-1 text-right">
{comment.length}/1000 caractères
</p>
</div>
{/* Submit Button */}
<button
type="submit"
disabled={submitting || rating === 0}
className="w-full px-6 py-3 border border-pixel-gold/50 bg-black/60 text-white uppercase text-sm tracking-widest rounded hover:bg-pixel-gold/10 hover:border-pixel-gold transition disabled:opacity-50 disabled:cursor-not-allowed"
>
{submitting
? "Enregistrement..."
: existingFeedback
? "Modifier le feedback"
: "Envoyer le feedback"}
</button>
</form>
</>
)}
</div>
</div>
</div>
);
}