diff --git a/app/layout.tsx b/app/layout.tsx index 45ec66e..51f2691 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,4 +1,5 @@ import type { Metadata } from "next"; +import type { ReactNode } from "react"; import { Orbitron, Rajdhani } from "next/font/google"; import "./globals.css"; import SessionProvider from "@/components/SessionProvider"; @@ -23,7 +24,7 @@ export const metadata: Metadata = { export default function RootLayout({ children, }: Readonly<{ - children: React.ReactNode; + children: ReactNode; }>) { return ( diff --git a/app/login/page.tsx b/app/login/page.tsx index a749255..07627b5 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useState, type FormEvent } from "react"; import { signIn } from "next-auth/react"; import { useRouter } from "next/navigation"; import Link from "next/link"; @@ -13,7 +13,7 @@ export default function LoginPage() { const [error, setError] = useState(""); const [loading, setLoading] = useState(false); - const handleSubmit = async (e: React.FormEvent) => { + const handleSubmit = async (e: FormEvent) => { e.preventDefault(); setError(""); setLoading(true); @@ -128,7 +128,7 @@ export default function LoginPage() { href="/register" className="text-pixel-gold hover:text-orange-400 transition" > - S'inscrire + S'inscrire

@@ -138,4 +138,3 @@ export default function LoginPage() { ); } - diff --git a/app/register/page.tsx b/app/register/page.tsx index 6b7a902..5f60495 100644 --- a/app/register/page.tsx +++ b/app/register/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useRef } from "react"; +import { useState, useRef, type ChangeEvent, type FormEvent } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import Navigation from "@/components/Navigation"; @@ -24,7 +24,7 @@ export default function RegisterPage() { const fileInputRef = useRef(null); const handleChange = ( - e: React.ChangeEvent + e: ChangeEvent ) => { setFormData({ ...formData, @@ -32,7 +32,7 @@ export default function RegisterPage() { }); }; - const handleAvatarUpload = async (e: React.ChangeEvent) => { + const handleAvatarUpload = async (e: ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; @@ -110,14 +110,14 @@ export default function RegisterPage() { setUserId(data.userId); setStep(2); - } catch (err) { + } catch { setError("Une erreur est survenue"); } finally { setLoading(false); } }; - const handleStep2Submit = async (e: React.FormEvent) => { + const handleStep2Submit = async (e: FormEvent) => { e.preventDefault(); setError(""); @@ -245,7 +245,7 @@ export default function RegisterPage() { htmlFor="username" className="block text-sm font-semibold text-gray-300 mb-2 uppercase tracking-wider" > - Nom d'utilisateur + Nom d'utilisateur - Nom d'utilisateur + Nom d'utilisateur

- Ces préférences s'appliquent à tous les utilisateurs + Ces préférences s'appliquent à tous les utilisateurs

{!isEditing && ( @@ -278,4 +278,3 @@ export default function AdminPanel({ initialPreferences }: AdminPanelProps) { ); } - diff --git a/components/EventsPageSection.tsx b/components/EventsPageSection.tsx index 3c35c63..ea745ea 100644 --- a/components/EventsPageSection.tsx +++ b/components/EventsPageSection.tsx @@ -533,7 +533,7 @@ export default function EventsPageSection({ ...prev, [eventId]: true, })); - } catch (err) { + } catch { setError("Une erreur est survenue"); } finally { setLoading((prev) => ({ ...prev, [eventId]: false })); @@ -559,7 +559,7 @@ export default function EventsPageSection({ ...prev, [eventId]: false, })); - } catch (err) { + } catch { setError("Une erreur est survenue"); } finally { setLoading((prev) => ({ ...prev, [eventId]: false })); @@ -600,7 +600,7 @@ export default function EventsPageSection({

Rejoignez-nous pour des événements tech passionnants, des - compétitions et des célébrations tout au long de l'année + compétitions et des célébrations tout au long de l'année

diff --git a/components/HeroSection.tsx b/components/HeroSection.tsx index 8fa3a7e..cc637af 100644 --- a/components/HeroSection.tsx +++ b/components/HeroSection.tsx @@ -100,11 +100,11 @@ export default function HeroSection() { {/* Description */}

- Dans un monde numérique de technologie de pointe, où les systèmes d'IA - évoluent et où d'anciens codes attendent d'être découverts. Partez - pour un voyage épique pour forger des alliances, conquérir des défis - et raconter votre histoire d'innovation au sein d'une communauté de - gaming tech florissante. + Dans un monde numérique de technologie de pointe, où les systèmes + d'IA évoluent et où d'anciens codes attendent d'être + découverts. Partez pour un voyage épique pour forger des alliances, + conquérir des défis et raconter votre histoire d'innovation au + sein d'une communauté de gaming tech florissante.

{/* Call-to-Action Buttons */} diff --git a/components/ImageSelector.tsx b/components/ImageSelector.tsx index c738c65..03a8675 100644 --- a/components/ImageSelector.tsx +++ b/components/ImageSelector.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect, useRef } from "react"; +import { useState, useEffect, useRef, type ChangeEvent } from "react"; interface ImageSelectorProps { value: string; @@ -14,7 +14,6 @@ export default function ImageSelector({ label, }: ImageSelectorProps) { const [availableImages, setAvailableImages] = useState([]); - const [loading, setLoading] = useState(false); const [uploading, setUploading] = useState(false); const [urlInput, setUrlInput] = useState(""); const [showGallery, setShowGallery] = useState(false); @@ -36,7 +35,7 @@ export default function ImageSelector({ } }; - const handleFileUpload = async (e: React.ChangeEvent) => { + const handleFileUpload = async (e: ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; @@ -153,9 +152,7 @@ export default function ImageSelector({ {/* Chemin de l'image */} - {value && ( -

{value}

- )} + {value &&

{value}

} @@ -204,4 +201,3 @@ export default function ImageSelector({ ); } - diff --git a/components/PlayerStats.tsx b/components/PlayerStats.tsx index 07adc84..c54ee8c 100644 --- a/components/PlayerStats.tsx +++ b/components/PlayerStats.tsx @@ -51,40 +51,48 @@ export default function PlayerStats({ initialUserData }: PlayerStatsProps) { .then((res) => res.json()) .then((data) => { if (data) { - setUserData({ - username: data.username || "Guest", - avatar: data.avatar, - hp: data.hp || 1000, - maxHp: data.maxHp || 1000, - xp: data.xp || 0, - maxXp: data.maxXp || 5000, - level: data.level || 1, + // Utiliser requestAnimationFrame pour éviter les cascades de rendu + requestAnimationFrame(() => { + setUserData({ + username: data.username || "Guest", + avatar: data.avatar, + hp: data.hp || 1000, + maxHp: data.maxHp || 1000, + xp: data.xp || 0, + maxXp: data.maxXp || 5000, + level: data.level || 1, + }); }); } }) .catch(() => { // Utiliser les données de session si l'API échoue - setUserData({ - username: session.user.username || "Guest", - avatar: null, - hp: 1000, - maxHp: 1000, - xp: 0, - maxXp: 5000, - level: 1, + requestAnimationFrame(() => { + setUserData({ + username: session.user.username || "Guest", + avatar: null, + hp: 1000, + maxHp: 1000, + xp: 0, + maxXp: 5000, + level: 1, + }); }); }); - } else { - setUserData(defaultUserData); + } else if (!initialUserData) { + // Utiliser requestAnimationFrame pour éviter les cascades de rendu + requestAnimationFrame(() => { + setUserData(defaultUserData); + }); } }, [session, initialUserData]); const { username, avatar, hp, maxHp, xp, maxXp, level } = userData; - + // Calculer les pourcentages cibles const targetHpPercentage = (hp / maxHp) * 100; const targetXpPercentage = (xp / maxXp) * 100; - + // Initialiser les pourcentages à 0 si on a des données initiales (pour l'animation) // Sinon utiliser directement les valeurs calculées const [hpPercentage, setHpPercentage] = useState( @@ -109,11 +117,13 @@ export default function PlayerStats({ initialUserData }: PlayerStatsProps) { clearTimeout(hpTimer); clearTimeout(xpTimer); }; - } else { - // Sinon, mettre à jour directement (pour les pages Client Components) + } + // Sinon, mettre à jour directement (pour les pages Client Components) + // Utiliser requestAnimationFrame pour éviter les cascades de rendu + requestAnimationFrame(() => { setHpPercentage(targetHpPercentage); setXpPercentage(targetXpPercentage); - } + }); }, [targetHpPercentage, targetXpPercentage, initialUserData]); const hpColor = @@ -126,7 +136,10 @@ export default function PlayerStats({ initialUserData }: PlayerStatsProps) { return (
{/* Avatar */} - +
{avatar ? ( {/* Username & Level */}
- +
{username}
@@ -155,7 +171,7 @@ export default function PlayerStats({ initialUserData }: PlayerStatsProps) { Lv.{level}
- + {/* Bars side by side */}
@@ -210,4 +226,3 @@ export default function PlayerStats({ initialUserData }: PlayerStatsProps) {
); } - diff --git a/components/ProfileForm.tsx b/components/ProfileForm.tsx index 935664b..96223b4 100644 --- a/components/ProfileForm.tsx +++ b/components/ProfileForm.tsx @@ -1,7 +1,6 @@ "use client"; -import { useState, useRef } from "react"; -import { useRouter } from "next/navigation"; +import { useState, useRef, type ChangeEvent } from "react"; type CharacterClass = | "WARRIOR" @@ -45,7 +44,6 @@ export default function ProfileForm({ initialProfile, backgroundImage, }: ProfileFormProps) { - const router = useRouter(); const [profile, setProfile] = useState(initialProfile); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); @@ -67,7 +65,7 @@ export default function ProfileForm({ const [confirmPassword, setConfirmPassword] = useState(""); const [changingPassword, setChangingPassword] = useState(false); - const handleAvatarUpload = async (e: React.ChangeEvent) => { + const handleAvatarUpload = async (e: ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; @@ -325,7 +323,7 @@ export default function ProfileForm({ {/* Username Field */}
{children} ); } - diff --git a/eslint.config.js b/eslint.config.js deleted file mode 100644 index 8ebcaff..0000000 --- a/eslint.config.js +++ /dev/null @@ -1,62 +0,0 @@ -import js from '@eslint/js'; -import tseslint from '@typescript-eslint/eslint-plugin'; -import tsparser from '@typescript-eslint/parser'; -import react from 'eslint-plugin-react'; -import reactHooks from 'eslint-plugin-react-hooks'; -import globals from 'globals'; - -export default [ - js.configs.recommended, - { - files: ['**/*.{ts,tsx}'], - languageOptions: { - parser: tsparser, - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - ecmaFeatures: { - jsx: true, - }, - }, - globals: { - ...globals.node, - ...globals.browser, - }, - }, - plugins: { - '@typescript-eslint': tseslint, - react, - 'react-hooks': reactHooks, - }, - rules: { - ...tseslint.configs.recommended.rules, - ...react.configs.recommended.rules, - ...reactHooks.configs.recommended.rules, - 'react/react-in-jsx-scope': 'off', - 'react/prop-types': 'off', - '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], - '@typescript-eslint/no-explicit-any': 'warn', - 'react/no-unescaped-entities': 'warn', - 'react/no-unknown-property': ['error', { ignore: ['jsx'] }], - 'react-hooks/set-state-in-effect': 'warn', - '@typescript-eslint/triple-slash-reference': 'off', - }, - settings: { - react: { - version: 'detect', - }, - }, - }, - { - ignores: [ - 'node_modules/**', - '.next/**', - 'out/**', - 'build/**', - 'dist/**', - '*.config.js', - 'prisma/generated/**', - ], - }, -]; - diff --git a/hooks/usePreferences.ts b/hooks/usePreferences.ts index 2d0b71b..ab477f0 100644 --- a/hooks/usePreferences.ts +++ b/hooks/usePreferences.ts @@ -48,7 +48,10 @@ export function useBackgroundImage( if (preferences) { const imageKey = `${page}Background` as keyof Preferences; const customImage = preferences[imageKey]; - setBackgroundImage(customImage || defaultImage); + // Utiliser requestAnimationFrame pour éviter les cascades de rendu + requestAnimationFrame(() => { + setBackgroundImage(customImage || defaultImage); + }); } }, [preferences, page, defaultImage]);