Add profile and house background preferences to SitePreferences: Extend SitePreferences model and related services to include profileBackground and houseBackground fields. Update API and UI components to support new background settings, enhancing user customization options.

This commit is contained in:
Julien Froidefond
2025-12-22 08:54:51 +01:00
parent 14c767cfc0
commit 9bcafe54d3
18 changed files with 295 additions and 10 deletions

View File

@@ -11,6 +11,8 @@ interface SitePreferences {
eventsBackground: string | null;
leaderboardBackground: string | null;
challengesBackground: string | null;
profileBackground: string | null;
houseBackground: string | null;
eventRegistrationPoints?: number;
}
@@ -23,6 +25,8 @@ const DEFAULT_IMAGES = {
events: "/got-2.jpg",
leaderboard: "/leaderboard-bg.jpg",
challenges: "/got-2.jpg",
profile: "/got-background.jpg",
houses: "/got-2.jpg",
};
export default function BackgroundPreferences({
@@ -64,6 +68,14 @@ export default function BackgroundPreferences({
initialPreferences.challengesBackground,
DEFAULT_IMAGES.challenges
),
profileBackground: getFormValue(
initialPreferences.profileBackground,
DEFAULT_IMAGES.profile
),
houseBackground: getFormValue(
initialPreferences.houseBackground,
DEFAULT_IMAGES.houses
),
}),
[initialPreferences]
);
@@ -101,6 +113,14 @@ export default function BackgroundPreferences({
formData.challengesBackground,
DEFAULT_IMAGES.challenges
),
profileBackground: getApiValue(
formData.profileBackground,
DEFAULT_IMAGES.profile
),
houseBackground: getApiValue(
formData.houseBackground,
DEFAULT_IMAGES.houses
),
};
const result = await updateSitePreferences(apiData);
@@ -125,6 +145,14 @@ export default function BackgroundPreferences({
result.data.challengesBackground,
DEFAULT_IMAGES.challenges
),
profileBackground: getFormValue(
result.data.profileBackground,
DEFAULT_IMAGES.profile
),
houseBackground: getFormValue(
result.data.houseBackground,
DEFAULT_IMAGES.houses
),
});
setIsEditing(false);
} else {
@@ -157,6 +185,14 @@ export default function BackgroundPreferences({
preferences.challengesBackground,
DEFAULT_IMAGES.challenges
),
profileBackground: getFormValue(
preferences.profileBackground,
DEFAULT_IMAGES.profile
),
houseBackground: getFormValue(
preferences.houseBackground,
DEFAULT_IMAGES.houses
),
});
}
};
@@ -226,6 +262,26 @@ export default function BackgroundPreferences({
}
label="Background Challenges"
/>
<ImageSelector
value={formData.profileBackground}
onChange={(url) =>
setFormData({
...formData,
profileBackground: url,
})
}
label="Background Profile"
/>
<ImageSelector
value={formData.houseBackground}
onChange={(url) =>
setFormData({
...formData,
houseBackground: url,
})
}
label="Background Houses"
/>
<div className="flex flex-col sm:flex-row gap-2 pt-4">
<Button onClick={handleSave} variant="success" size="md">
Enregistrer
@@ -461,6 +517,118 @@ export default function BackgroundPreferences({
);
})()}
</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">
Profile:
</span>
{(() => {
const currentImage =
preferences?.profileBackground &&
preferences.profileBackground.trim() !== ""
? preferences.profileBackground
: DEFAULT_IMAGES.profile;
const isDefault =
!preferences?.profileBackground ||
preferences.profileBackground.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="Profile background"
className="w-full h-full object-cover"
onError={(e) => {
const target = e.currentTarget;
const currentSrc = target.src;
const fallbackSrc = "/got-background.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">
Houses:
</span>
{(() => {
const currentImage =
preferences?.houseBackground &&
preferences.houseBackground.trim() !== ""
? preferences.houseBackground
: DEFAULT_IMAGES.houses;
const isDefault =
!preferences?.houseBackground ||
preferences.houseBackground.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="Houses 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>