Enhance image upload and background management: Update Docker configuration to create a dedicated backgrounds directory for uploaded images, modify API routes to handle background images specifically, and improve README documentation to reflect these changes. Additionally, refactor components to utilize the new Avatar component for consistent avatar rendering across the application.
Some checks failed
Deploy with Docker Compose / deploy (push) Failing after 33s
Some checks failed
Deploy with Docker Compose / deploy (push) Failing after 33s
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import ImageSelector from "@/components/ImageSelector";
|
||||
import UserManagement from "@/components/UserManagement";
|
||||
import EventManagement from "@/components/EventManagement";
|
||||
import FeedbackManagement from "@/components/FeedbackManagement";
|
||||
import BackgroundPreferences from "@/components/BackgroundPreferences";
|
||||
|
||||
interface SitePreferences {
|
||||
id: string;
|
||||
@@ -22,149 +22,58 @@ type AdminSection = "preferences" | "users" | "events" | "feedbacks";
|
||||
export default function AdminPanel({ initialPreferences }: AdminPanelProps) {
|
||||
const [activeSection, setActiveSection] =
|
||||
useState<AdminSection>("preferences");
|
||||
const [preferences, setPreferences] = useState<SitePreferences | null>(
|
||||
initialPreferences
|
||||
);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [formData, setFormData] = useState({
|
||||
homeBackground: initialPreferences.homeBackground || "",
|
||||
eventsBackground: initialPreferences.eventsBackground || "",
|
||||
leaderboardBackground: initialPreferences.leaderboardBackground || "",
|
||||
});
|
||||
|
||||
const handleEdit = () => {
|
||||
setIsEditing(true);
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
const response = await fetch("/api/admin/preferences", {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setPreferences(data);
|
||||
setIsEditing(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating preferences:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsEditing(false);
|
||||
if (preferences) {
|
||||
setFormData({
|
||||
homeBackground: preferences.homeBackground || "",
|
||||
eventsBackground: preferences.eventsBackground || "",
|
||||
leaderboardBackground: preferences.leaderboardBackground || "",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="relative w-full min-h-screen flex flex-col items-center overflow-hidden pt-24 pb-16">
|
||||
<div className="relative z-10 w-full max-w-6xl mx-auto px-4 sm:px-8 py-16">
|
||||
<h1 className="text-2xl sm:text-4xl font-gaming font-black mb-8 text-center break-words">
|
||||
<div className="relative z-10 w-full max-w-6xl mx-auto px-8 py-16">
|
||||
<h1 className="text-4xl font-gaming font-black mb-8 text-center">
|
||||
<span className="bg-gradient-to-r from-pixel-gold via-orange-400 to-pixel-gold bg-clip-text text-transparent">
|
||||
ADMIN
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{/* Navigation Tabs */}
|
||||
<div className="mb-8">
|
||||
{/* Mobile: Grid layout */}
|
||||
<div className="grid grid-cols-2 sm:hidden gap-2">
|
||||
<button
|
||||
onClick={() => setActiveSection("preferences")}
|
||||
className={`px-3 py-2.5 border uppercase text-xs tracking-wider rounded transition ${
|
||||
activeSection === "preferences"
|
||||
? "border-pixel-gold bg-pixel-gold/20 text-pixel-gold font-semibold"
|
||||
: "border-pixel-gold/30 bg-black/60 text-gray-400 hover:border-pixel-gold/50"
|
||||
}`}
|
||||
>
|
||||
Préférences
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveSection("users")}
|
||||
className={`px-3 py-2.5 border uppercase text-xs tracking-wider rounded transition ${
|
||||
activeSection === "users"
|
||||
? "border-pixel-gold bg-pixel-gold/20 text-pixel-gold font-semibold"
|
||||
: "border-pixel-gold/30 bg-black/60 text-gray-400 hover:border-pixel-gold/50"
|
||||
}`}
|
||||
>
|
||||
Utilisateurs
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveSection("events")}
|
||||
className={`px-3 py-2.5 border uppercase text-xs tracking-wider rounded transition ${
|
||||
activeSection === "events"
|
||||
? "border-pixel-gold bg-pixel-gold/20 text-pixel-gold font-semibold"
|
||||
: "border-pixel-gold/30 bg-black/60 text-gray-400 hover:border-pixel-gold/50"
|
||||
}`}
|
||||
>
|
||||
Événements
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveSection("feedbacks")}
|
||||
className={`px-3 py-2.5 border uppercase text-xs tracking-wider rounded transition ${
|
||||
activeSection === "feedbacks"
|
||||
? "border-pixel-gold bg-pixel-gold/20 text-pixel-gold font-semibold"
|
||||
: "border-pixel-gold/30 bg-black/60 text-gray-400 hover:border-pixel-gold/50"
|
||||
}`}
|
||||
>
|
||||
Feedbacks
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Desktop: Horizontal tabs */}
|
||||
<div className="hidden sm:flex gap-4 justify-center">
|
||||
<button
|
||||
onClick={() => setActiveSection("preferences")}
|
||||
className={`px-6 py-3 border uppercase text-xs tracking-widest rounded transition whitespace-nowrap ${
|
||||
activeSection === "preferences"
|
||||
? "border-pixel-gold bg-pixel-gold/10 text-pixel-gold"
|
||||
: "border-pixel-gold/30 bg-black/60 text-gray-400 hover:border-pixel-gold/50"
|
||||
}`}
|
||||
>
|
||||
Préférences UI
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveSection("users")}
|
||||
className={`px-6 py-3 border uppercase text-xs tracking-widest rounded transition whitespace-nowrap ${
|
||||
activeSection === "users"
|
||||
? "border-pixel-gold bg-pixel-gold/10 text-pixel-gold"
|
||||
: "border-pixel-gold/30 bg-black/60 text-gray-400 hover:border-pixel-gold/50"
|
||||
}`}
|
||||
>
|
||||
Utilisateurs
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveSection("events")}
|
||||
className={`px-6 py-3 border uppercase text-xs tracking-widest rounded transition whitespace-nowrap ${
|
||||
activeSection === "events"
|
||||
? "border-pixel-gold bg-pixel-gold/10 text-pixel-gold"
|
||||
: "border-pixel-gold/30 bg-black/60 text-gray-400 hover:border-pixel-gold/50"
|
||||
}`}
|
||||
>
|
||||
Événements
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveSection("feedbacks")}
|
||||
className={`px-6 py-3 border uppercase text-xs tracking-widest rounded transition whitespace-nowrap ${
|
||||
activeSection === "feedbacks"
|
||||
? "border-pixel-gold bg-pixel-gold/10 text-pixel-gold"
|
||||
: "border-pixel-gold/30 bg-black/60 text-gray-400 hover:border-pixel-gold/50"
|
||||
}`}
|
||||
>
|
||||
Feedbacks
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex gap-4 mb-8 justify-center">
|
||||
<button
|
||||
onClick={() => setActiveSection("preferences")}
|
||||
className={`px-6 py-3 border uppercase text-xs tracking-widest rounded transition ${
|
||||
activeSection === "preferences"
|
||||
? "border-pixel-gold bg-pixel-gold/10 text-pixel-gold"
|
||||
: "border-pixel-gold/30 bg-black/60 text-gray-400 hover:border-pixel-gold/50"
|
||||
}`}
|
||||
>
|
||||
Préférences UI
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveSection("users")}
|
||||
className={`px-6 py-3 border uppercase text-xs tracking-widest rounded transition ${
|
||||
activeSection === "users"
|
||||
? "border-pixel-gold bg-pixel-gold/10 text-pixel-gold"
|
||||
: "border-pixel-gold/30 bg-black/60 text-gray-400 hover:border-pixel-gold/50"
|
||||
}`}
|
||||
>
|
||||
Utilisateurs
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveSection("events")}
|
||||
className={`px-6 py-3 border uppercase text-xs tracking-widest rounded transition ${
|
||||
activeSection === "events"
|
||||
? "border-pixel-gold bg-pixel-gold/10 text-pixel-gold"
|
||||
: "border-pixel-gold/30 bg-black/60 text-gray-400 hover:border-pixel-gold/50"
|
||||
}`}
|
||||
>
|
||||
Événements
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveSection("feedbacks")}
|
||||
className={`px-6 py-3 border uppercase text-xs tracking-widest rounded transition ${
|
||||
activeSection === "feedbacks"
|
||||
? "border-pixel-gold bg-pixel-gold/10 text-pixel-gold"
|
||||
: "border-pixel-gold/30 bg-black/60 text-gray-400 hover:border-pixel-gold/50"
|
||||
}`}
|
||||
>
|
||||
Feedbacks
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{activeSection === "preferences" && (
|
||||
@@ -173,156 +82,13 @@ export default function AdminPanel({ initialPreferences }: AdminPanelProps) {
|
||||
Préférences UI Globales
|
||||
</h2>
|
||||
<div className="space-y-4">
|
||||
<div className="bg-black/60 border border-pixel-gold/20 rounded p-3 sm:p-4">
|
||||
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-3 mb-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<h3 className="text-pixel-gold font-bold text-base sm:text-lg break-words">
|
||||
Images de fond du site
|
||||
</h3>
|
||||
<p className="text-gray-400 text-xs sm:text-sm">
|
||||
Ces préférences s'appliquent à tous les utilisateurs
|
||||
</p>
|
||||
</div>
|
||||
{!isEditing && (
|
||||
<button
|
||||
onClick={handleEdit}
|
||||
className="px-3 sm:px-4 py-2 border border-pixel-gold/50 bg-black/60 text-white uppercase text-[10px] sm:text-xs tracking-widest rounded hover:bg-pixel-gold/10 transition whitespace-nowrap flex-shrink-0"
|
||||
>
|
||||
Modifier
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isEditing ? (
|
||||
<div className="space-y-6">
|
||||
<ImageSelector
|
||||
value={formData.homeBackground}
|
||||
onChange={(url) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
homeBackground: url,
|
||||
})
|
||||
}
|
||||
label="Background Home"
|
||||
/>
|
||||
<ImageSelector
|
||||
value={formData.eventsBackground}
|
||||
onChange={(url) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
eventsBackground: url,
|
||||
})
|
||||
}
|
||||
label="Background Events"
|
||||
/>
|
||||
<ImageSelector
|
||||
value={formData.leaderboardBackground}
|
||||
onChange={(url) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
leaderboardBackground: url,
|
||||
})
|
||||
}
|
||||
label="Background Leaderboard"
|
||||
/>
|
||||
<div className="flex flex-col sm:flex-row gap-2 pt-4">
|
||||
<button
|
||||
onClick={handleSave}
|
||||
className="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"
|
||||
>
|
||||
Enregistrer
|
||||
</button>
|
||||
<button
|
||||
onClick={handleCancel}
|
||||
className="px-4 py-2 border border-gray-600/50 bg-gray-900/20 text-gray-400 uppercase text-xs tracking-widest rounded hover:bg-gray-900/30 transition"
|
||||
>
|
||||
Annuler
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4">
|
||||
<span className="text-pixel-gold font-bold text-sm sm:text-base min-w-0 sm:min-w-[120px] flex-shrink-0">
|
||||
Home:
|
||||
</span>
|
||||
{preferences?.homeBackground ? (
|
||||
<div className="flex items-center gap-2 sm:gap-3 min-w-0 flex-1">
|
||||
<img
|
||||
src={preferences.homeBackground}
|
||||
alt="Home background"
|
||||
className="w-16 h-10 sm:w-20 sm:h-12 object-cover rounded border border-pixel-gold/30 flex-shrink-0"
|
||||
onError={(e) => {
|
||||
e.currentTarget.src = "/got-2.jpg";
|
||||
}}
|
||||
/>
|
||||
<span className="text-xs text-gray-400 truncate min-w-0">
|
||||
{preferences.homeBackground}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-gray-400 text-sm sm:text-base">
|
||||
Par défaut
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4">
|
||||
<span className="text-pixel-gold font-bold text-sm sm:text-base min-w-0 sm:min-w-[120px] flex-shrink-0">
|
||||
Events:
|
||||
</span>
|
||||
{preferences?.eventsBackground ? (
|
||||
<div className="flex items-center gap-2 sm:gap-3 min-w-0 flex-1">
|
||||
<img
|
||||
src={preferences.eventsBackground}
|
||||
alt="Events background"
|
||||
className="w-16 h-10 sm:w-20 sm:h-12 object-cover rounded border border-pixel-gold/30 flex-shrink-0"
|
||||
onError={(e) => {
|
||||
e.currentTarget.src = "/got-2.jpg";
|
||||
}}
|
||||
/>
|
||||
<span className="text-xs text-gray-400 truncate min-w-0">
|
||||
{preferences.eventsBackground}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-gray-400 text-sm sm:text-base">
|
||||
Par défaut
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4">
|
||||
<span className="text-pixel-gold font-bold text-sm sm:text-base min-w-0 sm:min-w-[120px] flex-shrink-0">
|
||||
Leaderboard:
|
||||
</span>
|
||||
{preferences?.leaderboardBackground ? (
|
||||
<div className="flex items-center gap-2 sm:gap-3 min-w-0 flex-1">
|
||||
<img
|
||||
src={preferences.leaderboardBackground}
|
||||
alt="Leaderboard background"
|
||||
className="w-16 h-10 sm:w-20 sm:h-12 object-cover rounded border border-pixel-gold/30 flex-shrink-0"
|
||||
onError={(e) => {
|
||||
e.currentTarget.src = "/got-2.jpg";
|
||||
}}
|
||||
/>
|
||||
<span className="text-xs text-gray-400 truncate min-w-0">
|
||||
{preferences.leaderboardBackground}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-gray-400 text-sm sm:text-base">
|
||||
Par défaut
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<BackgroundPreferences initialPreferences={initialPreferences} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeSection === "users" && (
|
||||
<div className="bg-black/80 border border-pixel-gold/30 rounded-lg p-4 sm:p-6 backdrop-blur-sm">
|
||||
<div className="bg-black/80 border border-pixel-gold/30 rounded-lg p-6 backdrop-blur-sm">
|
||||
<h2 className="text-2xl font-gaming font-bold mb-6 text-pixel-gold">
|
||||
Gestion des Utilisateurs
|
||||
</h2>
|
||||
@@ -331,7 +97,7 @@ export default function AdminPanel({ initialPreferences }: AdminPanelProps) {
|
||||
)}
|
||||
|
||||
{activeSection === "events" && (
|
||||
<div className="bg-black/80 border border-pixel-gold/30 rounded-lg p-4 sm:p-6 backdrop-blur-sm">
|
||||
<div className="bg-black/80 border border-pixel-gold/30 rounded-lg p-6 backdrop-blur-sm">
|
||||
<h2 className="text-2xl font-gaming font-bold mb-6 text-pixel-gold">
|
||||
Gestion des Événements
|
||||
</h2>
|
||||
@@ -340,7 +106,7 @@ export default function AdminPanel({ initialPreferences }: AdminPanelProps) {
|
||||
)}
|
||||
|
||||
{activeSection === "feedbacks" && (
|
||||
<div className="bg-black/80 border border-pixel-gold/30 rounded-lg p-4 sm:p-6 backdrop-blur-sm">
|
||||
<div className="bg-black/80 border border-pixel-gold/30 rounded-lg p-6 backdrop-blur-sm">
|
||||
<h2 className="text-2xl font-gaming font-bold mb-6 text-pixel-gold">
|
||||
Gestion des Feedbacks
|
||||
</h2>
|
||||
|
||||
Reference in New Issue
Block a user