Refactor ProfileForm component: Improve code readability by formatting error messages and UI elements. Add default avatar selection feature for user profile customization, enhancing user experience. Update button labels for clarity during avatar upload and password modification.

This commit is contained in:
Julien Froidefond
2025-12-09 14:26:13 +01:00
parent 67131f6470
commit b8f4672206
9 changed files with 147 additions and 14 deletions

View File

@@ -147,7 +147,9 @@ export default function ProfileForm({
setTimeout(() => setSuccess(null), 3000);
} else {
const errorData = await response.json();
setError(errorData.error || "Erreur lors de la modification du mot de passe");
setError(
errorData.error || "Erreur lors de la modification du mot de passe"
);
}
} catch (err) {
console.error("Error changing password:", err);
@@ -238,6 +240,45 @@ export default function ProfileForm({
</div>
)}
</div>
{/* Avatars par défaut */}
<div className="flex flex-col items-center gap-3">
<label className="block text-pixel-gold text-xs uppercase tracking-widest mb-2">
Avatars par défaut
</label>
<div className="flex gap-3">
{[
"/avatar-1.jpg",
"/avatar-2.jpg",
"/avatar-3.jpg",
"/avatar-4.jpg",
"/avatar-5.jpg",
"/avatar-6.jpg",
].map((defaultAvatar) => (
<button
key={defaultAvatar}
type="button"
onClick={() => {
setAvatar(defaultAvatar);
setSuccess("Avatar sélectionné");
setTimeout(() => setSuccess(null), 3000);
}}
className={`w-16 h-16 rounded-full border-2 overflow-hidden transition ${
avatar === defaultAvatar
? "border-pixel-gold scale-110"
: "border-pixel-gold/30 hover:border-pixel-gold/50"
}`}
>
<img
src={defaultAvatar}
alt="Avatar par défaut"
className="w-full h-full object-cover"
/>
</button>
))}
</div>
</div>
<div>
<input
ref={fileInputRef}
@@ -251,7 +292,9 @@ export default function ProfileForm({
htmlFor="avatar-upload"
className="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 cursor-pointer inline-block"
>
{uploadingAvatar ? "Upload en cours..." : "Changer l'avatar"}
{uploadingAvatar
? "Upload en cours..."
: "Upload un avatar custom"}
</label>
</div>
</div>
@@ -270,9 +313,7 @@ export default function ProfileForm({
minLength={3}
maxLength={20}
/>
<p className="text-gray-500 text-xs mt-1">
3-20 caractères
</p>
<p className="text-gray-500 text-xs mt-1">3-20 caractères</p>
</div>
{/* Stats Display */}
@@ -283,13 +324,17 @@ export default function ProfileForm({
<div className="grid grid-cols-2 gap-4 mb-4">
<div className="bg-black/40 border border-pixel-gold/20 rounded p-4">
<div className="text-gray-400 text-xs uppercase mb-1">Score</div>
<div className="text-gray-400 text-xs uppercase mb-1">
Score
</div>
<div className="text-pixel-gold text-xl font-bold">
{formatNumber(profile.score)}
</div>
</div>
<div className="bg-black/40 border border-pixel-gold/20 rounded p-4">
<div className="text-gray-400 text-xs uppercase mb-1">Niveau</div>
<div className="text-gray-400 text-xs uppercase mb-1">
Niveau
</div>
<div className="text-pixel-gold text-xl font-bold">
Lv.{profile.level}
</div>
@@ -300,7 +345,9 @@ export default function ProfileForm({
<div className="mb-4">
<div className="flex justify-between text-xs text-gray-400 mb-1">
<span>HP</span>
<span>{profile.hp} / {profile.maxHp}</span>
<span>
{profile.hp} / {profile.maxHp}
</span>
</div>
<div className="relative h-3 bg-gray-900 border border-gray-700 rounded overflow-hidden">
<div
@@ -314,7 +361,9 @@ export default function ProfileForm({
<div>
<div className="flex justify-between text-xs text-gray-400 mb-1">
<span>XP</span>
<span>{formatNumber(profile.xp)} / {formatNumber(profile.maxXp)}</span>
<span>
{formatNumber(profile.xp)} / {formatNumber(profile.maxXp)}
</span>
</div>
<div className="relative h-3 bg-gray-900 border border-pixel-gold/30 rounded overflow-hidden">
<div
@@ -432,7 +481,9 @@ export default function ProfileForm({
disabled={changingPassword}
className="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 disabled:opacity-50 disabled:cursor-not-allowed"
>
{changingPassword ? "Modification..." : "Modifier le mot de passe"}
{changingPassword
? "Modification..."
: "Modifier le mot de passe"}
</button>
</div>
</form>
@@ -443,4 +494,3 @@ export default function ProfileForm({
</section>
);
}

View File

@@ -0,0 +1,84 @@
"use client";
import { useEffect } from "react";
export default function WebpackErrorHandler() {
useEffect(() => {
// Listen for unhandled errors
const handleError = (event: ErrorEvent) => {
const error = event.error || event.message || "";
const errorString = String(error);
// Check if it's a webpack module loading error
if (
errorString.includes("Cannot read properties of undefined") &&
(errorString.includes("reading 'call'") ||
errorString.includes("webpack") ||
errorString.includes("react-server-dom-webpack"))
) {
console.warn(
"Webpack module loading error detected, forcing page reload..."
);
// Clear all caches and reload
if (typeof window !== "undefined") {
// Clear service worker cache if exists
if ("serviceWorker" in navigator) {
navigator.serviceWorker.getRegistrations().then((registrations) => {
registrations.forEach((registration) => {
registration.unregister();
});
});
}
// Clear all caches
if ("caches" in window) {
caches.keys().then((names) => {
names.forEach((name) => {
caches.delete(name);
});
});
}
// Force hard reload after a short delay
setTimeout(() => {
window.location.reload();
}, 100);
}
}
};
// Listen for unhandled promise rejections
const handleRejection = (event: PromiseRejectionEvent) => {
const error = event.reason || "";
const errorString = String(error);
if (
errorString.includes("Cannot read properties of undefined") &&
(errorString.includes("reading 'call'") ||
errorString.includes("webpack") ||
errorString.includes("react-server-dom-webpack"))
) {
console.warn(
"Webpack module loading error detected in promise, forcing page reload..."
);
if (typeof window !== "undefined") {
setTimeout(() => {
window.location.reload();
}, 100);
}
}
};
window.addEventListener("error", handleError);
window.addEventListener("unhandledrejection", handleRejection);
return () => {
window.removeEventListener("error", handleError);
window.removeEventListener("unhandledrejection", handleRejection);
};
}, []);
return null;
}