Compare commits
2 Commits
88d377e7b2
...
44be5d2e98
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44be5d2e98 | ||
|
|
13b8971cc7 |
@@ -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}`}>
|
||||||
|
|||||||
@@ -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'inscrire
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -138,4 +138,3 @@ export default function LoginPage() {
|
|||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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'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'utilisateur
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="username-step2"
|
id="username-step2"
|
||||||
|
|||||||
@@ -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'appliquent à tous les utilisateurs
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{!isEditing && (
|
{!isEditing && (
|
||||||
@@ -278,4 +278,3 @@ export default function AdminPanel({ initialPreferences }: AdminPanelProps) {
|
|||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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'année
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -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, où les systèmes d'IA
|
Dans un monde numérique de technologie de pointe, où les systèmes
|
||||||
évoluent et où d'anciens codes attendent d'être découverts. Partez
|
d'IA évoluent et où d'anciens codes attendent d'ê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'innovation au
|
||||||
gaming tech florissante.
|
sein d'une communauté de gaming tech florissante.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Call-to-Action Buttons */}
|
{/* Call-to-Action Buttons */}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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'utilisateur
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
63
eslint.config.mjs
Normal file
63
eslint.config.mjs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
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,
|
||||||
|
React: 'readonly',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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/**',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
@@ -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]);
|
||||||
|
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -6,7 +6,7 @@
|
|||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "eslint . --ext .ts,.tsx",
|
||||||
"db:seed": "tsx prisma/seed.ts"
|
"db:seed": "tsx prisma/seed.ts"
|
||||||
},
|
},
|
||||||
"prisma": {
|
"prisma": {
|
||||||
@@ -23,12 +23,20 @@
|
|||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.39.1",
|
||||||
"@types/bcryptjs": "^3.0.0",
|
"@types/bcryptjs": "^3.0.0",
|
||||||
"@types/better-sqlite3": "^7.6.13",
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
"@types/node": "^22.0.0",
|
"@types/node": "^22.0.0",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-dom": "^19.0.0",
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.49.0",
|
||||||
|
"@typescript-eslint/parser": "^8.49.0",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
|
"eslint": "^9.39.1",
|
||||||
|
"eslint-config-next": "^16.0.8",
|
||||||
|
"eslint-plugin-react": "^7.37.5",
|
||||||
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
|
"globals": "^16.5.0",
|
||||||
"postcss": "^8.4.40",
|
"postcss": "^8.4.40",
|
||||||
"prisma": "^7.1.0",
|
"prisma": "^7.1.0",
|
||||||
"tailwindcss": "^3.4.7",
|
"tailwindcss": "^3.4.7",
|
||||||
|
|||||||
2790
pnpm-lock.yaml
generated
2790
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user