468 lines
17 KiB
TypeScript
468 lines
17 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect, useMemo } from "react";
|
|
import ImageSelector from "@/components/layout/ImageSelector";
|
|
import { updateSitePreferences } from "@/actions/admin/preferences";
|
|
import { Button, Card } from "@/components/ui";
|
|
|
|
interface SitePreferences {
|
|
id: string;
|
|
homeBackground: string | null;
|
|
eventsBackground: string | null;
|
|
leaderboardBackground: string | null;
|
|
challengesBackground: string | null;
|
|
}
|
|
|
|
interface BackgroundPreferencesProps {
|
|
initialPreferences: SitePreferences;
|
|
}
|
|
|
|
const DEFAULT_IMAGES = {
|
|
home: "/got-2.jpg",
|
|
events: "/got-2.jpg",
|
|
leaderboard: "/leaderboard-bg.jpg",
|
|
challenges: "/got-2.jpg",
|
|
};
|
|
|
|
export default function BackgroundPreferences({
|
|
initialPreferences,
|
|
}: BackgroundPreferencesProps) {
|
|
// Helper pour obtenir la valeur à afficher dans le formulaire
|
|
const getFormValue = (
|
|
dbValue: string | null | undefined,
|
|
defaultImage: string
|
|
) => {
|
|
return dbValue && dbValue.trim() !== "" ? dbValue : defaultImage;
|
|
};
|
|
|
|
// Helper pour obtenir la valeur à envoyer à l'API
|
|
const getApiValue = (formValue: string, defaultImage: string) => {
|
|
return formValue === defaultImage ? "" : formValue;
|
|
};
|
|
|
|
const [preferences, setPreferences] = useState<SitePreferences | null>(
|
|
initialPreferences
|
|
);
|
|
const [isEditing, setIsEditing] = useState(false);
|
|
|
|
const computedFormData = useMemo(
|
|
() => ({
|
|
homeBackground: getFormValue(
|
|
initialPreferences.homeBackground,
|
|
DEFAULT_IMAGES.home
|
|
),
|
|
eventsBackground: getFormValue(
|
|
initialPreferences.eventsBackground,
|
|
DEFAULT_IMAGES.events
|
|
),
|
|
leaderboardBackground: getFormValue(
|
|
initialPreferences.leaderboardBackground,
|
|
DEFAULT_IMAGES.leaderboard
|
|
),
|
|
challengesBackground: getFormValue(
|
|
initialPreferences.challengesBackground,
|
|
DEFAULT_IMAGES.challenges
|
|
),
|
|
}),
|
|
[initialPreferences]
|
|
);
|
|
|
|
const [formData, setFormData] = useState(computedFormData);
|
|
|
|
// Synchroniser les préférences quand initialPreferences change
|
|
useEffect(() => {
|
|
setPreferences(initialPreferences);
|
|
setFormData(computedFormData);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [initialPreferences]);
|
|
|
|
const handleEdit = () => {
|
|
setIsEditing(true);
|
|
};
|
|
|
|
const handleSave = async () => {
|
|
try {
|
|
// Convertir les valeurs du formulaire en valeurs API ("" si c'est l'image par défaut)
|
|
const apiData = {
|
|
homeBackground: getApiValue(
|
|
formData.homeBackground,
|
|
DEFAULT_IMAGES.home
|
|
),
|
|
eventsBackground: getApiValue(
|
|
formData.eventsBackground,
|
|
DEFAULT_IMAGES.events
|
|
),
|
|
leaderboardBackground: getApiValue(
|
|
formData.leaderboardBackground,
|
|
DEFAULT_IMAGES.leaderboard
|
|
),
|
|
challengesBackground: getApiValue(
|
|
formData.challengesBackground,
|
|
DEFAULT_IMAGES.challenges
|
|
),
|
|
};
|
|
|
|
const result = await updateSitePreferences(apiData);
|
|
|
|
if (result.success && result.data) {
|
|
setPreferences(result.data);
|
|
// Réinitialiser formData avec les nouvelles valeurs (ou images par défaut)
|
|
setFormData({
|
|
homeBackground: getFormValue(
|
|
result.data.homeBackground,
|
|
DEFAULT_IMAGES.home
|
|
),
|
|
eventsBackground: getFormValue(
|
|
result.data.eventsBackground,
|
|
DEFAULT_IMAGES.events
|
|
),
|
|
leaderboardBackground: getFormValue(
|
|
result.data.leaderboardBackground,
|
|
DEFAULT_IMAGES.leaderboard
|
|
),
|
|
challengesBackground: getFormValue(
|
|
result.data.challengesBackground,
|
|
DEFAULT_IMAGES.challenges
|
|
),
|
|
});
|
|
setIsEditing(false);
|
|
} else {
|
|
console.error("Error updating preferences:", result.error);
|
|
alert(result.error || "Erreur lors de la mise à jour");
|
|
}
|
|
} catch (error) {
|
|
console.error("Error updating preferences:", error);
|
|
alert("Erreur lors de la mise à jour");
|
|
}
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
setIsEditing(false);
|
|
if (preferences) {
|
|
setFormData({
|
|
homeBackground: getFormValue(
|
|
preferences.homeBackground,
|
|
DEFAULT_IMAGES.home
|
|
),
|
|
eventsBackground: getFormValue(
|
|
preferences.eventsBackground,
|
|
DEFAULT_IMAGES.events
|
|
),
|
|
leaderboardBackground: getFormValue(
|
|
preferences.leaderboardBackground,
|
|
DEFAULT_IMAGES.leaderboard
|
|
),
|
|
challengesBackground: getFormValue(
|
|
preferences.challengesBackground,
|
|
DEFAULT_IMAGES.challenges
|
|
),
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Card variant="default" className="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}
|
|
variant="primary"
|
|
size="sm"
|
|
className="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"
|
|
/>
|
|
<ImageSelector
|
|
value={formData.challengesBackground}
|
|
onChange={(url) =>
|
|
setFormData({
|
|
...formData,
|
|
challengesBackground: url,
|
|
})
|
|
}
|
|
label="Background Challenges"
|
|
/>
|
|
<div className="flex flex-col sm:flex-row gap-2 pt-4">
|
|
<Button onClick={handleSave} variant="success" size="md">
|
|
Enregistrer
|
|
</Button>
|
|
<Button onClick={handleCancel} variant="secondary" size="md">
|
|
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>
|
|
{(() => {
|
|
const currentImage =
|
|
preferences?.homeBackground &&
|
|
preferences.homeBackground.trim() !== ""
|
|
? preferences.homeBackground
|
|
: DEFAULT_IMAGES.home;
|
|
const isDefault =
|
|
!preferences?.homeBackground ||
|
|
preferences.homeBackground.trim() === "";
|
|
|
|
return (
|
|
<div className="flex items-center gap-2 sm:gap-3 min-w-0 flex-1">
|
|
<div className="relative w-16 h-10 sm:w-20 sm:h-12 rounded border border-pixel-gold/30 overflow-hidden bg-black/60 flex-shrink-0">
|
|
<img
|
|
src={currentImage}
|
|
alt="Home background"
|
|
className="w-full h-full object-cover"
|
|
onError={(e) => {
|
|
const target = e.currentTarget;
|
|
const currentSrc = target.src;
|
|
const fallbackSrc = "/got-2.jpg";
|
|
if (!currentSrc.includes(fallbackSrc)) {
|
|
target.src = fallbackSrc;
|
|
} else {
|
|
target.style.display = "none";
|
|
const fallbackDiv =
|
|
target.nextElementSibling as HTMLElement;
|
|
if (fallbackDiv) {
|
|
fallbackDiv.classList.remove("hidden");
|
|
}
|
|
}
|
|
}}
|
|
/>
|
|
<div className="absolute inset-0 flex items-center justify-center bg-black/60 text-gray-500 text-xs hidden">
|
|
No image
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col min-w-0 flex-1">
|
|
<span className="text-xs text-gray-400 truncate min-w-0">
|
|
{isDefault ? "Par défaut: " : ""}
|
|
{currentImage}
|
|
</span>
|
|
{isDefault && (
|
|
<span className="text-[10px] text-gray-500 italic">
|
|
(Image par défaut)
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})()}
|
|
</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>
|
|
{(() => {
|
|
const currentImage =
|
|
preferences?.eventsBackground &&
|
|
preferences.eventsBackground.trim() !== ""
|
|
? preferences.eventsBackground
|
|
: DEFAULT_IMAGES.events;
|
|
const isDefault =
|
|
!preferences?.eventsBackground ||
|
|
preferences.eventsBackground.trim() === "";
|
|
|
|
return (
|
|
<div className="flex items-center gap-2 sm:gap-3 min-w-0 flex-1">
|
|
<div className="relative w-16 h-10 sm:w-20 sm:h-12 rounded border border-pixel-gold/30 overflow-hidden bg-black/60 flex-shrink-0">
|
|
<img
|
|
src={currentImage}
|
|
alt="Events background"
|
|
className="w-full h-full object-cover"
|
|
onError={(e) => {
|
|
const target = e.currentTarget;
|
|
const currentSrc = target.src;
|
|
const fallbackSrc = "/got-2.jpg";
|
|
if (!currentSrc.includes(fallbackSrc)) {
|
|
target.src = fallbackSrc;
|
|
} else {
|
|
target.style.display = "none";
|
|
const fallbackDiv =
|
|
target.nextElementSibling as HTMLElement;
|
|
if (fallbackDiv) {
|
|
fallbackDiv.classList.remove("hidden");
|
|
}
|
|
}
|
|
}}
|
|
/>
|
|
<div className="absolute inset-0 flex items-center justify-center bg-black/60 text-gray-500 text-xs hidden">
|
|
No image
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col min-w-0 flex-1">
|
|
<span className="text-xs text-gray-400 truncate min-w-0">
|
|
{isDefault ? "Par défaut: " : ""}
|
|
{currentImage}
|
|
</span>
|
|
{isDefault && (
|
|
<span className="text-[10px] text-gray-500 italic">
|
|
(Image par défaut)
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})()}
|
|
</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>
|
|
{(() => {
|
|
const currentImage =
|
|
preferences?.leaderboardBackground &&
|
|
preferences.leaderboardBackground.trim() !== ""
|
|
? preferences.leaderboardBackground
|
|
: DEFAULT_IMAGES.leaderboard;
|
|
const isDefault =
|
|
!preferences?.leaderboardBackground ||
|
|
preferences.leaderboardBackground.trim() === "";
|
|
|
|
return (
|
|
<div className="flex items-center gap-2 sm:gap-3 min-w-0 flex-1">
|
|
<div className="relative w-16 h-10 sm:w-20 sm:h-12 rounded border border-pixel-gold/30 overflow-hidden bg-black/60 flex-shrink-0">
|
|
<img
|
|
src={currentImage}
|
|
alt="Leaderboard background"
|
|
className="w-full h-full object-cover"
|
|
onError={(e) => {
|
|
const target = e.currentTarget;
|
|
const currentSrc = target.src;
|
|
const fallbackSrc = "/got-2.jpg";
|
|
if (!currentSrc.includes(fallbackSrc)) {
|
|
target.src = fallbackSrc;
|
|
} else {
|
|
target.style.display = "none";
|
|
const fallbackDiv =
|
|
target.nextElementSibling as HTMLElement;
|
|
if (fallbackDiv) {
|
|
fallbackDiv.classList.remove("hidden");
|
|
}
|
|
}
|
|
}}
|
|
/>
|
|
<div className="absolute inset-0 flex items-center justify-center bg-black/60 text-gray-500 text-xs hidden">
|
|
No image
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col min-w-0 flex-1">
|
|
<span className="text-xs text-gray-400 truncate min-w-0">
|
|
{isDefault ? "Par défaut: " : ""}
|
|
{currentImage}
|
|
</span>
|
|
{isDefault && (
|
|
<span className="text-[10px] text-gray-500 italic">
|
|
(Image par défaut)
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})()}
|
|
</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">
|
|
Challenges:
|
|
</span>
|
|
{(() => {
|
|
const currentImage =
|
|
preferences?.challengesBackground &&
|
|
preferences.challengesBackground.trim() !== ""
|
|
? preferences.challengesBackground
|
|
: DEFAULT_IMAGES.challenges;
|
|
const isDefault =
|
|
!preferences?.challengesBackground ||
|
|
preferences.challengesBackground.trim() === "";
|
|
|
|
return (
|
|
<div className="flex items-center gap-2 sm:gap-3 min-w-0 flex-1">
|
|
<div className="relative w-16 h-10 sm:w-20 sm:h-12 rounded border border-pixel-gold/30 overflow-hidden bg-black/60 flex-shrink-0">
|
|
<img
|
|
src={currentImage}
|
|
alt="Challenges background"
|
|
className="w-full h-full object-cover"
|
|
onError={(e) => {
|
|
const target = e.currentTarget;
|
|
const currentSrc = target.src;
|
|
const fallbackSrc = "/got-2.jpg";
|
|
if (!currentSrc.includes(fallbackSrc)) {
|
|
target.src = fallbackSrc;
|
|
} else {
|
|
target.style.display = "none";
|
|
const fallbackDiv =
|
|
target.nextElementSibling as HTMLElement;
|
|
if (fallbackDiv) {
|
|
fallbackDiv.classList.remove("hidden");
|
|
}
|
|
}
|
|
}}
|
|
/>
|
|
<div className="absolute inset-0 flex items-center justify-center bg-black/60 text-gray-500 text-xs hidden">
|
|
No image
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col min-w-0 flex-1">
|
|
<span className="text-xs text-gray-400 truncate min-w-0">
|
|
{isDefault ? "Par défaut: " : ""}
|
|
{currentImage}
|
|
</span>
|
|
{isDefault && (
|
|
<span className="text-[10px] text-gray-500 italic">
|
|
(Image par défaut)
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})()}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</Card>
|
|
);
|
|
}
|