Remove ESLint configuration file and update type imports across components: Deleted eslint.config.js to streamline project setup. Updated type imports in layout, login, register, and other components to use direct imports for improved clarity and consistency. Enhanced error handling in various components and replaced apostrophes with HTML entities for better rendering.

This commit is contained in:
Julien Froidefond
2025-12-10 06:01:34 +01:00
parent 13b8971cc7
commit 44be5d2e98
12 changed files with 75 additions and 130 deletions

View File

@@ -1,4 +1,5 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import type { ReactNode } from "react";
import { Orbitron, Rajdhani } from "next/font/google"; import { Orbitron, Rajdhani } from "next/font/google";
import "./globals.css"; import "./globals.css";
import SessionProvider from "@/components/SessionProvider"; import SessionProvider from "@/components/SessionProvider";
@@ -23,7 +24,7 @@ export const metadata: Metadata = {
export default function RootLayout({ export default function RootLayout({
children, children,
}: Readonly<{ }: Readonly<{
children: React.ReactNode; children: ReactNode;
}>) { }>) {
return ( return (
<html lang="fr" className={`${orbitron.variable} ${rajdhani.variable}`}> <html lang="fr" className={`${orbitron.variable} ${rajdhani.variable}`}>

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { useState } from "react"; import { useState, type FormEvent } from "react";
import { signIn } from "next-auth/react"; import { signIn } from "next-auth/react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import Link from "next/link"; import Link from "next/link";
@@ -13,7 +13,7 @@ export default function LoginPage() {
const [error, setError] = useState(""); const [error, setError] = useState("");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: FormEvent) => {
e.preventDefault(); e.preventDefault();
setError(""); setError("");
setLoading(true); setLoading(true);
@@ -128,7 +128,7 @@ export default function LoginPage() {
href="/register" href="/register"
className="text-pixel-gold hover:text-orange-400 transition" className="text-pixel-gold hover:text-orange-400 transition"
> >
S'inscrire S&apos;inscrire
</Link> </Link>
</p> </p>
</div> </div>
@@ -138,4 +138,3 @@ export default function LoginPage() {
</main> </main>
); );
} }

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { useState, useRef } from "react"; import { useState, useRef, type ChangeEvent, type FormEvent } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import Link from "next/link"; import Link from "next/link";
import Navigation from "@/components/Navigation"; import Navigation from "@/components/Navigation";
@@ -24,7 +24,7 @@ export default function RegisterPage() {
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
const handleChange = ( const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement> e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => { ) => {
setFormData({ setFormData({
...formData, ...formData,
@@ -32,7 +32,7 @@ export default function RegisterPage() {
}); });
}; };
const handleAvatarUpload = async (e: React.ChangeEvent<HTMLInputElement>) => { const handleAvatarUpload = async (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]; const file = e.target.files?.[0];
if (!file) return; if (!file) return;
@@ -110,14 +110,14 @@ export default function RegisterPage() {
setUserId(data.userId); setUserId(data.userId);
setStep(2); setStep(2);
} catch (err) { } catch {
setError("Une erreur est survenue"); setError("Une erreur est survenue");
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
const handleStep2Submit = async (e: React.FormEvent) => { const handleStep2Submit = async (e: FormEvent) => {
e.preventDefault(); e.preventDefault();
setError(""); setError("");
@@ -245,7 +245,7 @@ export default function RegisterPage() {
htmlFor="username" htmlFor="username"
className="block text-sm font-semibold text-gray-300 mb-2 uppercase tracking-wider" className="block text-sm font-semibold text-gray-300 mb-2 uppercase tracking-wider"
> >
Nom d'utilisateur Nom d&apos;utilisateur
</label> </label>
<input <input
id="username" id="username"
@@ -413,7 +413,7 @@ export default function RegisterPage() {
htmlFor="username-step2" htmlFor="username-step2"
className="block text-sm font-semibold text-gray-300 mb-2 uppercase tracking-wider" className="block text-sm font-semibold text-gray-300 mb-2 uppercase tracking-wider"
> >
Nom d'utilisateur Nom d&apos;utilisateur
</label> </label>
<input <input
id="username-step2" id="username-step2"

View File

@@ -122,7 +122,7 @@ export default function AdminPanel({ initialPreferences }: AdminPanelProps) {
Images de fond du site Images de fond du site
</h3> </h3>
<p className="text-gray-400 text-sm"> <p className="text-gray-400 text-sm">
Ces préférences s'appliquent à tous les utilisateurs Ces préférences s&apos;appliquent à tous les utilisateurs
</p> </p>
</div> </div>
{!isEditing && ( {!isEditing && (
@@ -278,4 +278,3 @@ export default function AdminPanel({ initialPreferences }: AdminPanelProps) {
</section> </section>
); );
} }

View File

@@ -533,7 +533,7 @@ export default function EventsPageSection({
...prev, ...prev,
[eventId]: true, [eventId]: true,
})); }));
} catch (err) { } catch {
setError("Une erreur est survenue"); setError("Une erreur est survenue");
} finally { } finally {
setLoading((prev) => ({ ...prev, [eventId]: false })); setLoading((prev) => ({ ...prev, [eventId]: false }));
@@ -559,7 +559,7 @@ export default function EventsPageSection({
...prev, ...prev,
[eventId]: false, [eventId]: false,
})); }));
} catch (err) { } catch {
setError("Une erreur est survenue"); setError("Une erreur est survenue");
} finally { } finally {
setLoading((prev) => ({ ...prev, [eventId]: false })); setLoading((prev) => ({ ...prev, [eventId]: false }));
@@ -600,7 +600,7 @@ export default function EventsPageSection({
</div> </div>
<p className="text-gray-400 text-sm max-w-2xl mx-auto"> <p className="text-gray-400 text-sm max-w-2xl mx-auto">
Rejoignez-nous pour des événements tech passionnants, des 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&apos;année
</p> </p>
</div> </div>

View File

@@ -100,11 +100,11 @@ export default function HeroSection() {
{/* Description */} {/* Description */}
<p className="text-white text-base md:text-lg max-w-3xl mx-auto mb-12 leading-relaxed px-4"> <p className="text-white text-base md:text-lg max-w-3xl mx-auto mb-12 leading-relaxed px-4">
Dans un monde numérique de technologie de pointe, les systèmes d'IA Dans un monde numérique de technologie de pointe, les systèmes
évoluent et où d'anciens codes attendent d'être découverts. Partez d&apos;IA évoluent et d&apos;anciens codes attendent d&apos;être
pour un voyage épique pour forger des alliances, conquérir des défis découverts. Partez pour un voyage épique pour forger des alliances,
et raconter votre histoire d'innovation au sein d'une communauté de conquérir des défis et raconter votre histoire d&apos;innovation au
gaming tech florissante. sein d&apos;une communauté de gaming tech florissante.
</p> </p>
{/* Call-to-Action Buttons */} {/* Call-to-Action Buttons */}

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { useState, useEffect, useRef } from "react"; import { useState, useEffect, useRef, type ChangeEvent } from "react";
interface ImageSelectorProps { interface ImageSelectorProps {
value: string; value: string;
@@ -14,7 +14,6 @@ export default function ImageSelector({
label, label,
}: ImageSelectorProps) { }: ImageSelectorProps) {
const [availableImages, setAvailableImages] = useState<string[]>([]); const [availableImages, setAvailableImages] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
const [uploading, setUploading] = useState(false); const [uploading, setUploading] = useState(false);
const [urlInput, setUrlInput] = useState(""); const [urlInput, setUrlInput] = useState("");
const [showGallery, setShowGallery] = useState(false); const [showGallery, setShowGallery] = useState(false);
@@ -36,7 +35,7 @@ export default function ImageSelector({
} }
}; };
const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => { const handleFileUpload = async (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]; const file = e.target.files?.[0];
if (!file) return; if (!file) return;
@@ -153,9 +152,7 @@ export default function ImageSelector({
</div> </div>
{/* Chemin de l'image */} {/* Chemin de l'image */}
{value && ( {value && <p className="text-xs text-gray-400 truncate">{value}</p>}
<p className="text-xs text-gray-400 truncate">{value}</p>
)}
</div> </div>
</div> </div>
@@ -204,4 +201,3 @@ export default function ImageSelector({
</div> </div>
); );
} }

View File

@@ -51,40 +51,48 @@ export default function PlayerStats({ initialUserData }: PlayerStatsProps) {
.then((res) => res.json()) .then((res) => res.json())
.then((data) => { .then((data) => {
if (data) { if (data) {
setUserData({ // Utiliser requestAnimationFrame pour éviter les cascades de rendu
username: data.username || "Guest", requestAnimationFrame(() => {
avatar: data.avatar, setUserData({
hp: data.hp || 1000, username: data.username || "Guest",
maxHp: data.maxHp || 1000, avatar: data.avatar,
xp: data.xp || 0, hp: data.hp || 1000,
maxXp: data.maxXp || 5000, maxHp: data.maxHp || 1000,
level: data.level || 1, xp: data.xp || 0,
maxXp: data.maxXp || 5000,
level: data.level || 1,
});
}); });
} }
}) })
.catch(() => { .catch(() => {
// Utiliser les données de session si l'API échoue // Utiliser les données de session si l'API échoue
setUserData({ requestAnimationFrame(() => {
username: session.user.username || "Guest", setUserData({
avatar: null, username: session.user.username || "Guest",
hp: 1000, avatar: null,
maxHp: 1000, hp: 1000,
xp: 0, maxHp: 1000,
maxXp: 5000, xp: 0,
level: 1, maxXp: 5000,
level: 1,
});
}); });
}); });
} else { } else if (!initialUserData) {
setUserData(defaultUserData); // Utiliser requestAnimationFrame pour éviter les cascades de rendu
requestAnimationFrame(() => {
setUserData(defaultUserData);
});
} }
}, [session, initialUserData]); }, [session, initialUserData]);
const { username, avatar, hp, maxHp, xp, maxXp, level } = userData; const { username, avatar, hp, maxHp, xp, maxXp, level } = userData;
// Calculer les pourcentages cibles // Calculer les pourcentages cibles
const targetHpPercentage = (hp / maxHp) * 100; const targetHpPercentage = (hp / maxHp) * 100;
const targetXpPercentage = (xp / maxXp) * 100; const targetXpPercentage = (xp / maxXp) * 100;
// Initialiser les pourcentages à 0 si on a des données initiales (pour l'animation) // Initialiser les pourcentages à 0 si on a des données initiales (pour l'animation)
// Sinon utiliser directement les valeurs calculées // Sinon utiliser directement les valeurs calculées
const [hpPercentage, setHpPercentage] = useState( const [hpPercentage, setHpPercentage] = useState(
@@ -109,11 +117,13 @@ export default function PlayerStats({ initialUserData }: PlayerStatsProps) {
clearTimeout(hpTimer); clearTimeout(hpTimer);
clearTimeout(xpTimer); 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); setHpPercentage(targetHpPercentage);
setXpPercentage(targetXpPercentage); setXpPercentage(targetXpPercentage);
} });
}, [targetHpPercentage, targetXpPercentage, initialUserData]); }, [targetHpPercentage, targetXpPercentage, initialUserData]);
const hpColor = const hpColor =
@@ -126,7 +136,10 @@ export default function PlayerStats({ initialUserData }: PlayerStatsProps) {
return ( return (
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{/* Avatar */} {/* Avatar */}
<Link href="/profile" className="cursor-pointer hover:opacity-80 transition-opacity"> <Link
href="/profile"
className="cursor-pointer hover:opacity-80 transition-opacity"
>
<div className="w-10 h-10 rounded-full border border-pixel-gold/20 overflow-hidden bg-gray-900 flex items-center justify-center"> <div className="w-10 h-10 rounded-full border border-pixel-gold/20 overflow-hidden bg-gray-900 flex items-center justify-center">
{avatar ? ( {avatar ? (
<img <img
@@ -146,7 +159,10 @@ export default function PlayerStats({ initialUserData }: PlayerStatsProps) {
<div className="flex flex-col gap-1.5 min-w-[200px]"> <div className="flex flex-col gap-1.5 min-w-[200px]">
{/* Username & Level */} {/* Username & Level */}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Link href="/profile" className="cursor-pointer hover:text-pixel-gold/80 transition-colors"> <Link
href="/profile"
className="cursor-pointer hover:text-pixel-gold/80 transition-colors"
>
<div className="text-pixel-gold font-gaming font-bold text-sm tracking-wider"> <div className="text-pixel-gold font-gaming font-bold text-sm tracking-wider">
{username} {username}
</div> </div>
@@ -155,7 +171,7 @@ export default function PlayerStats({ initialUserData }: PlayerStatsProps) {
Lv.{level} Lv.{level}
</div> </div>
</div> </div>
{/* Bars side by side */} {/* Bars side by side */}
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -210,4 +226,3 @@ export default function PlayerStats({ initialUserData }: PlayerStatsProps) {
</div> </div>
); );
} }

View File

@@ -1,7 +1,6 @@
"use client"; "use client";
import { useState, useRef } from "react"; import { useState, useRef, type ChangeEvent } from "react";
import { useRouter } from "next/navigation";
type CharacterClass = type CharacterClass =
| "WARRIOR" | "WARRIOR"
@@ -45,7 +44,6 @@ export default function ProfileForm({
initialProfile, initialProfile,
backgroundImage, backgroundImage,
}: ProfileFormProps) { }: ProfileFormProps) {
const router = useRouter();
const [profile, setProfile] = useState<UserProfile>(initialProfile); const [profile, setProfile] = useState<UserProfile>(initialProfile);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@@ -67,7 +65,7 @@ export default function ProfileForm({
const [confirmPassword, setConfirmPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState("");
const [changingPassword, setChangingPassword] = useState(false); const [changingPassword, setChangingPassword] = useState(false);
const handleAvatarUpload = async (e: React.ChangeEvent<HTMLInputElement>) => { const handleAvatarUpload = async (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]; const file = e.target.files?.[0];
if (!file) return; if (!file) return;
@@ -325,7 +323,7 @@ export default function ProfileForm({
{/* Username Field */} {/* Username Field */}
<div> <div>
<label className="block text-pixel-gold text-sm uppercase tracking-widest mb-2"> <label className="block text-pixel-gold text-sm uppercase tracking-widest mb-2">
Nom d'utilisateur Nom d&apos;utilisateur
</label> </label>
<input <input
type="text" type="text"

View File

@@ -1,16 +1,12 @@
"use client"; "use client";
import { type ReactNode } from "react";
import { SessionProvider as NextAuthSessionProvider } from "next-auth/react"; import { SessionProvider as NextAuthSessionProvider } from "next-auth/react";
export default function SessionProvider({ export default function SessionProvider({ children }: { children: ReactNode }) {
children,
}: {
children: React.ReactNode;
}) {
return ( return (
<NextAuthSessionProvider basePath="/api/auth"> <NextAuthSessionProvider basePath="/api/auth">
{children} {children}
</NextAuthSessionProvider> </NextAuthSessionProvider>
); );
} }

View File

@@ -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/**',
],
},
];

View File

@@ -48,7 +48,10 @@ export function useBackgroundImage(
if (preferences) { if (preferences) {
const imageKey = `${page}Background` as keyof Preferences; const imageKey = `${page}Background` as keyof Preferences;
const customImage = preferences[imageKey]; const customImage = preferences[imageKey];
setBackgroundImage(customImage || defaultImage); // Utiliser requestAnimationFrame pour éviter les cascades de rendu
requestAnimationFrame(() => {
setBackgroundImage(customImage || defaultImage);
});
} }
}, [preferences, page, defaultImage]); }, [preferences, page, defaultImage]);