Implement event feedback functionality: Add EventFeedback model to Prisma schema, enabling users to submit ratings and comments for events. Update EventsPageSection and AdminPanel components to support feedback management, including UI for submitting feedback and viewing existing feedbacks. Refactor registration logic to retrieve all user registrations for improved feedback handling.
This commit is contained in:
259
app/feedback/[eventId]/page.tsx
Normal file
259
app/feedback/[eventId]/page.tsx
Normal file
@@ -0,0 +1,259 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, type FormEvent } from "react";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useRouter, useParams } from "next/navigation";
|
||||
import Navigation from "@/components/Navigation";
|
||||
import { useBackgroundImage } from "@/hooks/usePreferences";
|
||||
|
||||
interface Event {
|
||||
id: string;
|
||||
name: string;
|
||||
date: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface Feedback {
|
||||
id: string;
|
||||
rating: number;
|
||||
comment: string | null;
|
||||
}
|
||||
|
||||
export default function FeedbackPage() {
|
||||
const { status } = useSession();
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const eventId = params?.eventId as string;
|
||||
const backgroundImage = useBackgroundImage("home", "/got-2.jpg");
|
||||
|
||||
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("");
|
||||
|
||||
const fetchEventAndFeedback = async () => {
|
||||
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 || "");
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
setError("Erreur lors du chargement des données");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (status === "unauthenticated") {
|
||||
router.push(`/login?redirect=/feedback/${eventId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (status === "authenticated" && eventId) {
|
||||
fetchEventAndFeedback();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [status, eventId, router]);
|
||||
|
||||
const handleSubmit = async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
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);
|
||||
|
||||
// Rediriger après 2 secondes
|
||||
setTimeout(() => {
|
||||
router.push("/events");
|
||||
}, 2000);
|
||||
} catch {
|
||||
setError("Erreur lors de l'enregistrement");
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (status === "loading" || loading) {
|
||||
return (
|
||||
<main className="min-h-screen bg-black relative">
|
||||
<Navigation />
|
||||
<section className="relative w-full min-h-screen flex flex-col items-center justify-center overflow-hidden pt-24">
|
||||
<div className="text-white">Chargement...</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
if (!event) {
|
||||
return (
|
||||
<main className="min-h-screen bg-black relative">
|
||||
<Navigation />
|
||||
<section className="relative w-full min-h-screen flex flex-col items-center justify-center overflow-hidden pt-24">
|
||||
<div className="text-red-400">Événement introuvable</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-black relative">
|
||||
<Navigation />
|
||||
<section className="relative w-full min-h-screen flex flex-col items-center justify-center overflow-hidden pt-24">
|
||||
{/* Background Image */}
|
||||
<div
|
||||
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||||
style={{
|
||||
backgroundImage: `url('${backgroundImage}')`,
|
||||
}}
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-black/70 via-black/60 to-black/80"></div>
|
||||
</div>
|
||||
|
||||
{/* Feedback Form */}
|
||||
<div className="relative z-10 w-full max-w-2xl mx-auto px-8">
|
||||
<div className="bg-black/80 border border-pixel-gold/30 rounded-lg p-8 backdrop-blur-sm">
|
||||
<h1 className="text-4xl font-gaming font-black mb-2 text-center">
|
||||
<span className="bg-gradient-to-r from-pixel-gold via-orange-400 to-pixel-gold bg-clip-text text-transparent">
|
||||
FEEDBACK
|
||||
</span>
|
||||
</h1>
|
||||
<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 ! Redirection...
|
||||
</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)}
|
||||
className={`text-4xl transition-transform hover:scale-110 ${
|
||||
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}
|
||||
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"
|
||||
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>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user