Refactor character class handling across components: Replace hardcoded character class definitions with a centralized CHARACTER_CLASSES import, enhancing maintainability and consistency. Update ProfileForm, Leaderboard, and LeaderboardSection components to utilize new utility functions for character class icons and names, improving code clarity and reducing duplication.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 2m48s
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 2m48s
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
|||||||
BackgroundSection,
|
BackgroundSection,
|
||||||
SectionTitle,
|
SectionTitle,
|
||||||
} from "@/components/ui";
|
} from "@/components/ui";
|
||||||
|
import { CHARACTER_CLASSES } from "@/lib/character-classes";
|
||||||
|
|
||||||
export default function RegisterPage() {
|
export default function RegisterPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -397,22 +398,7 @@ export default function RegisterPage() {
|
|||||||
Classe de Personnage (optionnel)
|
Classe de Personnage (optionnel)
|
||||||
</label>
|
</label>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
{[
|
{CHARACTER_CLASSES.map((cls) => (
|
||||||
{ value: "WARRIOR", name: "Guerrier", icon: "⚔️" },
|
|
||||||
{ value: "MAGE", name: "Mage", icon: "🔮" },
|
|
||||||
{ value: "ROGUE", name: "Voleur", icon: "🗡️" },
|
|
||||||
{ value: "RANGER", name: "Rôdeur", icon: "🏹" },
|
|
||||||
{ value: "PALADIN", name: "Paladin", icon: "🛡️" },
|
|
||||||
{ value: "ENGINEER", name: "Ingénieur", icon: "⚙️" },
|
|
||||||
{ value: "MERCHANT", name: "Marchand", icon: "💰" },
|
|
||||||
{ value: "SCHOLAR", name: "Érudit", icon: "📚" },
|
|
||||||
{ value: "BERSERKER", name: "Berserker", icon: "🔥" },
|
|
||||||
{
|
|
||||||
value: "NECROMANCER",
|
|
||||||
name: "Nécromancien",
|
|
||||||
icon: "💀",
|
|
||||||
},
|
|
||||||
].map((cls) => (
|
|
||||||
<button
|
<button
|
||||||
key={cls.value}
|
key={cls.value}
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Avatar } from "@/components/ui";
|
import { Avatar } from "@/components/ui";
|
||||||
|
import { getCharacterClassIcon, getCharacterClassName, type CharacterClass } from "@/lib/character-classes";
|
||||||
|
|
||||||
interface LeaderboardEntry {
|
interface LeaderboardEntry {
|
||||||
rank: number;
|
rank: number;
|
||||||
@@ -11,7 +12,7 @@ interface LeaderboardEntry {
|
|||||||
level: number;
|
level: number;
|
||||||
avatar?: string | null;
|
avatar?: string | null;
|
||||||
bio?: string | null;
|
bio?: string | null;
|
||||||
characterClass?: string | null;
|
characterClass?: CharacterClass | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format number with consistent locale to avoid hydration mismatch
|
// Format number with consistent locale to avoid hydration mismatch
|
||||||
@@ -98,18 +99,7 @@ export default function Leaderboard() {
|
|||||||
</span>
|
</span>
|
||||||
{entry.characterClass && (
|
{entry.characterClass && (
|
||||||
<span className="text-xs text-gray-400 uppercase tracking-wider">
|
<span className="text-xs text-gray-400 uppercase tracking-wider">
|
||||||
[{entry.characterClass === "WARRIOR" && "⚔️ Guerrier"}
|
[{getCharacterClassIcon(entry.characterClass)} {getCharacterClassName(entry.characterClass)}]
|
||||||
{entry.characterClass === "MAGE" && "🔮 Mage"}
|
|
||||||
{entry.characterClass === "ROGUE" && "🗡️ Voleur"}
|
|
||||||
{entry.characterClass === "RANGER" && "🏹 Rôdeur"}
|
|
||||||
{entry.characterClass === "PALADIN" && "🛡️ Paladin"}
|
|
||||||
{entry.characterClass === "ENGINEER" && "⚙️ Ingénieur"}
|
|
||||||
{entry.characterClass === "MERCHANT" && "💰 Marchand"}
|
|
||||||
{entry.characterClass === "SCHOLAR" && "📚 Érudit"}
|
|
||||||
{entry.characterClass === "BERSERKER" && "🔥 Berserker"}
|
|
||||||
{entry.characterClass === "NECROMANCER" &&
|
|
||||||
"💀 Nécromancien"}
|
|
||||||
]
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -177,34 +167,10 @@ export default function Leaderboard() {
|
|||||||
{selectedEntry.characterClass && (
|
{selectedEntry.characterClass && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-2xl">
|
<span className="text-2xl">
|
||||||
{selectedEntry.characterClass === "WARRIOR" && "⚔️"}
|
{getCharacterClassIcon(selectedEntry.characterClass)}
|
||||||
{selectedEntry.characterClass === "MAGE" && "🔮"}
|
|
||||||
{selectedEntry.characterClass === "ROGUE" && "🗡️"}
|
|
||||||
{selectedEntry.characterClass === "RANGER" && "🏹"}
|
|
||||||
{selectedEntry.characterClass === "PALADIN" && "🛡️"}
|
|
||||||
{selectedEntry.characterClass === "ENGINEER" && "⚙️"}
|
|
||||||
{selectedEntry.characterClass === "MERCHANT" && "💰"}
|
|
||||||
{selectedEntry.characterClass === "SCHOLAR" && "📚"}
|
|
||||||
{selectedEntry.characterClass === "BERSERKER" && "🔥"}
|
|
||||||
{selectedEntry.characterClass === "NECROMANCER" && "💀"}
|
|
||||||
</span>
|
</span>
|
||||||
<span className="text-lg font-bold text-pixel-gold uppercase tracking-wider">
|
<span className="text-lg font-bold text-pixel-gold uppercase tracking-wider">
|
||||||
{selectedEntry.characterClass === "WARRIOR" &&
|
{getCharacterClassName(selectedEntry.characterClass)}
|
||||||
"Guerrier"}
|
|
||||||
{selectedEntry.characterClass === "MAGE" && "Mage"}
|
|
||||||
{selectedEntry.characterClass === "ROGUE" && "Voleur"}
|
|
||||||
{selectedEntry.characterClass === "RANGER" && "Rôdeur"}
|
|
||||||
{selectedEntry.characterClass === "PALADIN" &&
|
|
||||||
"Paladin"}
|
|
||||||
{selectedEntry.characterClass === "ENGINEER" &&
|
|
||||||
"Ingénieur"}
|
|
||||||
{selectedEntry.characterClass === "MERCHANT" &&
|
|
||||||
"Marchand"}
|
|
||||||
{selectedEntry.characterClass === "SCHOLAR" && "Érudit"}
|
|
||||||
{selectedEntry.characterClass === "BERSERKER" &&
|
|
||||||
"Berserker"}
|
|
||||||
{selectedEntry.characterClass === "NECROMANCER" &&
|
|
||||||
"Nécromancien"}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ import {
|
|||||||
BackgroundSection,
|
BackgroundSection,
|
||||||
SectionTitle,
|
SectionTitle,
|
||||||
} from "@/components/ui";
|
} from "@/components/ui";
|
||||||
|
import {
|
||||||
|
getCharacterClassIcon,
|
||||||
|
getCharacterClassName,
|
||||||
|
type CharacterClass,
|
||||||
|
} from "@/lib/character-classes";
|
||||||
|
|
||||||
interface LeaderboardEntry {
|
interface LeaderboardEntry {
|
||||||
rank: number;
|
rank: number;
|
||||||
@@ -18,7 +23,7 @@ interface LeaderboardEntry {
|
|||||||
level: number;
|
level: number;
|
||||||
avatar?: string | null;
|
avatar?: string | null;
|
||||||
bio?: string | null;
|
bio?: string | null;
|
||||||
characterClass?: string | null;
|
characterClass?: CharacterClass | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LeaderboardSectionProps {
|
interface LeaderboardSectionProps {
|
||||||
@@ -111,16 +116,7 @@ export default function LeaderboardSection({
|
|||||||
</span>
|
</span>
|
||||||
{entry.characterClass && (
|
{entry.characterClass && (
|
||||||
<span className="text-xs text-gray-400 uppercase tracking-wider">
|
<span className="text-xs text-gray-400 uppercase tracking-wider">
|
||||||
[{entry.characterClass === "WARRIOR" && "⚔️"}
|
[{getCharacterClassIcon(entry.characterClass)}]
|
||||||
{entry.characterClass === "MAGE" && "🔮"}
|
|
||||||
{entry.characterClass === "ROGUE" && "🗡️"}
|
|
||||||
{entry.characterClass === "RANGER" && "🏹"}
|
|
||||||
{entry.characterClass === "PALADIN" && "🛡️"}
|
|
||||||
{entry.characterClass === "ENGINEER" && "⚙️"}
|
|
||||||
{entry.characterClass === "MERCHANT" && "💰"}
|
|
||||||
{entry.characterClass === "SCHOLAR" && "📚"}
|
|
||||||
{entry.characterClass === "BERSERKER" && "🔥"}
|
|
||||||
{entry.characterClass === "NECROMANCER" && "💀"}]
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{entry.rank <= 3 && (
|
{entry.rank <= 3 && (
|
||||||
@@ -190,32 +186,10 @@ export default function LeaderboardSection({
|
|||||||
{selectedEntry.characterClass && (
|
{selectedEntry.characterClass && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-2xl">
|
<span className="text-2xl">
|
||||||
{selectedEntry.characterClass === "WARRIOR" && "⚔️"}
|
{getCharacterClassIcon(selectedEntry.characterClass)}
|
||||||
{selectedEntry.characterClass === "MAGE" && "🔮"}
|
|
||||||
{selectedEntry.characterClass === "ROGUE" && "🗡️"}
|
|
||||||
{selectedEntry.characterClass === "RANGER" && "🏹"}
|
|
||||||
{selectedEntry.characterClass === "PALADIN" && "🛡️"}
|
|
||||||
{selectedEntry.characterClass === "ENGINEER" && "⚙️"}
|
|
||||||
{selectedEntry.characterClass === "MERCHANT" && "💰"}
|
|
||||||
{selectedEntry.characterClass === "SCHOLAR" && "📚"}
|
|
||||||
{selectedEntry.characterClass === "BERSERKER" && "🔥"}
|
|
||||||
{selectedEntry.characterClass === "NECROMANCER" && "💀"}
|
|
||||||
</span>
|
</span>
|
||||||
<span className="text-lg font-bold text-pixel-gold uppercase tracking-wider">
|
<span className="text-lg font-bold text-pixel-gold uppercase tracking-wider">
|
||||||
{selectedEntry.characterClass === "WARRIOR" && "Guerrier"}
|
{getCharacterClassName(selectedEntry.characterClass)}
|
||||||
{selectedEntry.characterClass === "MAGE" && "Mage"}
|
|
||||||
{selectedEntry.characterClass === "ROGUE" && "Voleur"}
|
|
||||||
{selectedEntry.characterClass === "RANGER" && "Rôdeur"}
|
|
||||||
{selectedEntry.characterClass === "PALADIN" && "Paladin"}
|
|
||||||
{selectedEntry.characterClass === "ENGINEER" &&
|
|
||||||
"Ingénieur"}
|
|
||||||
{selectedEntry.characterClass === "MERCHANT" &&
|
|
||||||
"Marchand"}
|
|
||||||
{selectedEntry.characterClass === "SCHOLAR" && "Érudit"}
|
|
||||||
{selectedEntry.characterClass === "BERSERKER" &&
|
|
||||||
"Berserker"}
|
|
||||||
{selectedEntry.characterClass === "NECROMANCER" &&
|
|
||||||
"Nécromancien"}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useRef, useTransition, type ChangeEvent } from "react";
|
import { useState, useRef, useTransition, type ChangeEvent } from "react";
|
||||||
import { Avatar, Input, Textarea, Button, Alert, Card, BackgroundSection, SectionTitle, ProgressBar } from "@/components/ui";
|
import {
|
||||||
|
Avatar,
|
||||||
|
Input,
|
||||||
|
Textarea,
|
||||||
|
Button,
|
||||||
|
Alert,
|
||||||
|
Card,
|
||||||
|
BackgroundSection,
|
||||||
|
SectionTitle,
|
||||||
|
ProgressBar,
|
||||||
|
} from "@/components/ui";
|
||||||
import { updateProfile } from "@/actions/profile/update-profile";
|
import { updateProfile } from "@/actions/profile/update-profile";
|
||||||
import { updatePassword } from "@/actions/profile/update-password";
|
import { updatePassword } from "@/actions/profile/update-password";
|
||||||
|
import {
|
||||||
type CharacterClass =
|
CHARACTER_CLASSES,
|
||||||
| "WARRIOR"
|
type CharacterClass,
|
||||||
| "MAGE"
|
} from "@/lib/character-classes";
|
||||||
| "ROGUE"
|
|
||||||
| "RANGER"
|
|
||||||
| "PALADIN"
|
|
||||||
| "ENGINEER"
|
|
||||||
| "MERCHANT"
|
|
||||||
| "SCHOLAR"
|
|
||||||
| "BERSERKER"
|
|
||||||
| "NECROMANCER"
|
|
||||||
| null;
|
|
||||||
|
|
||||||
interface UserProfile {
|
interface UserProfile {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -24,7 +25,7 @@ interface UserProfile {
|
|||||||
username: string;
|
username: string;
|
||||||
avatar: string | null;
|
avatar: string | null;
|
||||||
bio: string | null;
|
bio: string | null;
|
||||||
characterClass: CharacterClass;
|
characterClass: CharacterClass | null;
|
||||||
hp: number;
|
hp: number;
|
||||||
maxHp: number;
|
maxHp: number;
|
||||||
xp: number;
|
xp: number;
|
||||||
@@ -55,7 +56,7 @@ export default function ProfileForm({
|
|||||||
const [username, setUsername] = useState(initialProfile.username);
|
const [username, setUsername] = useState(initialProfile.username);
|
||||||
const [avatar, setAvatar] = useState<string | null>(initialProfile.avatar);
|
const [avatar, setAvatar] = useState<string | null>(initialProfile.avatar);
|
||||||
const [bio, setBio] = useState<string | null>(initialProfile.bio || null);
|
const [bio, setBio] = useState<string | null>(initialProfile.bio || null);
|
||||||
const [characterClass, setCharacterClass] = useState<CharacterClass>(
|
const [characterClass, setCharacterClass] = useState<CharacterClass | null>(
|
||||||
initialProfile.characterClass || null
|
initialProfile.characterClass || null
|
||||||
);
|
);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
@@ -120,12 +121,15 @@ export default function ProfileForm({
|
|||||||
if (result.success && result.data) {
|
if (result.success && result.data) {
|
||||||
setProfile({
|
setProfile({
|
||||||
...result.data,
|
...result.data,
|
||||||
createdAt: result.data.createdAt instanceof Date
|
createdAt:
|
||||||
? result.data.createdAt.toISOString()
|
result.data.createdAt instanceof Date
|
||||||
: result.data.createdAt,
|
? result.data.createdAt.toISOString()
|
||||||
|
: result.data.createdAt,
|
||||||
} as UserProfile);
|
} as UserProfile);
|
||||||
setBio(result.data.bio || null);
|
setBio(result.data.bio || null);
|
||||||
setCharacterClass(result.data.characterClass as CharacterClass || null);
|
setCharacterClass(
|
||||||
|
(result.data.characterClass as CharacterClass) || null
|
||||||
|
);
|
||||||
setSuccess("Profil mis à jour avec succès");
|
setSuccess("Profil mis à jour avec succès");
|
||||||
setTimeout(() => setSuccess(null), 3000);
|
setTimeout(() => setSuccess(null), 3000);
|
||||||
} else {
|
} else {
|
||||||
@@ -154,7 +158,9 @@ export default function ProfileForm({
|
|||||||
setShowPasswordForm(false);
|
setShowPasswordForm(false);
|
||||||
setTimeout(() => setSuccess(null), 3000);
|
setTimeout(() => setSuccess(null), 3000);
|
||||||
} else {
|
} else {
|
||||||
setError(result.error || "Erreur lors de la modification du mot de passe");
|
setError(
|
||||||
|
result.error || "Erreur lors de la modification du mot de passe"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -163,7 +169,12 @@ export default function ProfileForm({
|
|||||||
<BackgroundSection backgroundImage={backgroundImage}>
|
<BackgroundSection backgroundImage={backgroundImage}>
|
||||||
<div className="w-full max-w-4xl mx-auto px-8">
|
<div className="w-full max-w-4xl mx-auto px-8">
|
||||||
{/* Title Section */}
|
{/* Title Section */}
|
||||||
<SectionTitle variant="gradient" size="lg" subtitle="Gérez votre profil" className="mb-12">
|
<SectionTitle
|
||||||
|
variant="gradient"
|
||||||
|
size="lg"
|
||||||
|
subtitle="Gérez votre profil"
|
||||||
|
className="mb-12"
|
||||||
|
>
|
||||||
PROFIL
|
PROFIL
|
||||||
</SectionTitle>
|
</SectionTitle>
|
||||||
|
|
||||||
@@ -238,8 +249,15 @@ export default function ProfileForm({
|
|||||||
id="avatar-upload"
|
id="avatar-upload"
|
||||||
/>
|
/>
|
||||||
<label htmlFor="avatar-upload">
|
<label htmlFor="avatar-upload">
|
||||||
<Button variant="primary" size="md" as="span" className="cursor-pointer">
|
<Button
|
||||||
{uploadingAvatar ? "Upload en cours..." : "Upload un avatar custom"}
|
variant="primary"
|
||||||
|
size="md"
|
||||||
|
as="span"
|
||||||
|
className="cursor-pointer"
|
||||||
|
>
|
||||||
|
{uploadingAvatar
|
||||||
|
? "Upload en cours..."
|
||||||
|
: "Upload un avatar custom"}
|
||||||
</Button>
|
</Button>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -276,68 +294,7 @@ export default function ProfileForm({
|
|||||||
Classe de Personnage
|
Classe de Personnage
|
||||||
</label>
|
</label>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||||
{[
|
{CHARACTER_CLASSES.map((cls) => (
|
||||||
{
|
|
||||||
value: "WARRIOR",
|
|
||||||
name: "Guerrier",
|
|
||||||
icon: "⚔️",
|
|
||||||
desc: "Maître du combat au corps à corps",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "MAGE",
|
|
||||||
name: "Mage",
|
|
||||||
icon: "🔮",
|
|
||||||
desc: "Manipulateur des arcanes",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "ROGUE",
|
|
||||||
name: "Voleur",
|
|
||||||
icon: "🗡️",
|
|
||||||
desc: "Furtif et mortel",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "RANGER",
|
|
||||||
name: "Rôdeur",
|
|
||||||
icon: "🏹",
|
|
||||||
desc: "Chasseur des terres sauvages",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "PALADIN",
|
|
||||||
name: "Paladin",
|
|
||||||
icon: "🛡️",
|
|
||||||
desc: "Protecteur sacré",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "ENGINEER",
|
|
||||||
name: "Ingénieur",
|
|
||||||
icon: "⚙️",
|
|
||||||
desc: "Créateur d'artefacts",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "MERCHANT",
|
|
||||||
name: "Marchand",
|
|
||||||
icon: "💰",
|
|
||||||
desc: "Maître du commerce",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "SCHOLAR",
|
|
||||||
name: "Érudit",
|
|
||||||
icon: "📚",
|
|
||||||
desc: "Gardien du savoir",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "BERSERKER",
|
|
||||||
name: "Berserker",
|
|
||||||
icon: "🔥",
|
|
||||||
desc: "Rage destructrice",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "NECROMANCER",
|
|
||||||
name: "Nécromancien",
|
|
||||||
icon: "💀",
|
|
||||||
desc: "Maître des morts",
|
|
||||||
},
|
|
||||||
].map((cls) => (
|
|
||||||
<button
|
<button
|
||||||
key={cls.value}
|
key={cls.value}
|
||||||
type="button"
|
type="button"
|
||||||
@@ -435,8 +392,15 @@ export default function ProfileForm({
|
|||||||
|
|
||||||
{/* Submit Button */}
|
{/* Submit Button */}
|
||||||
<div className="flex justify-end gap-4 pt-4 border-t border-pixel-gold/20">
|
<div className="flex justify-end gap-4 pt-4 border-t border-pixel-gold/20">
|
||||||
<Button type="submit" variant="primary" size="md" disabled={isPending}>
|
<Button
|
||||||
{isPending ? "Enregistrement..." : "Enregistrer les modifications"}
|
type="submit"
|
||||||
|
variant="primary"
|
||||||
|
size="md"
|
||||||
|
disabled={isPending}
|
||||||
|
>
|
||||||
|
{isPending
|
||||||
|
? "Enregistrement..."
|
||||||
|
: "Enregistrer les modifications"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
118
lib/character-classes.ts
Normal file
118
lib/character-classes.ts
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
export type CharacterClass =
|
||||||
|
| "WARRIOR"
|
||||||
|
| "MAGE"
|
||||||
|
| "ROGUE"
|
||||||
|
| "RANGER"
|
||||||
|
| "PALADIN"
|
||||||
|
| "ENGINEER"
|
||||||
|
| "MERCHANT"
|
||||||
|
| "SCHOLAR"
|
||||||
|
| "BERSERKER"
|
||||||
|
| "NECROMANCER";
|
||||||
|
|
||||||
|
export interface CharacterClassConfig {
|
||||||
|
value: CharacterClass;
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
desc?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CHARACTER_CLASSES: CharacterClassConfig[] = [
|
||||||
|
{
|
||||||
|
value: "WARRIOR",
|
||||||
|
name: "Guerrier",
|
||||||
|
icon: "⚔️",
|
||||||
|
desc: "Maître du combat au corps à corps",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "MAGE",
|
||||||
|
name: "Mage",
|
||||||
|
icon: "🔮",
|
||||||
|
desc: "Manipulateur des arcanes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "ROGUE",
|
||||||
|
name: "Voleur",
|
||||||
|
icon: "🗡️",
|
||||||
|
desc: "Furtif et mortel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "RANGER",
|
||||||
|
name: "Rôdeur",
|
||||||
|
icon: "🏹",
|
||||||
|
desc: "Chasseur des terres sauvages",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "PALADIN",
|
||||||
|
name: "Paladin",
|
||||||
|
icon: "🛡️",
|
||||||
|
desc: "Protecteur sacré",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "ENGINEER",
|
||||||
|
name: "Ingénieur",
|
||||||
|
icon: "⚙️",
|
||||||
|
desc: "Créateur d'artefacts",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "MERCHANT",
|
||||||
|
name: "Marchand",
|
||||||
|
icon: "💰",
|
||||||
|
desc: "Maître du commerce",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "SCHOLAR",
|
||||||
|
name: "Érudit",
|
||||||
|
icon: "📚",
|
||||||
|
desc: "Gardien du savoir",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "BERSERKER",
|
||||||
|
name: "Berserker",
|
||||||
|
icon: "🔥",
|
||||||
|
desc: "Rage destructrice",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "NECROMANCER",
|
||||||
|
name: "Nécromancien",
|
||||||
|
icon: "💀",
|
||||||
|
desc: "Maître des morts",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const CHARACTER_CLASS_MAP: Record<CharacterClass, CharacterClassConfig> =
|
||||||
|
CHARACTER_CLASSES.reduce(
|
||||||
|
(acc, cls) => {
|
||||||
|
acc[cls.value] = cls;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<CharacterClass, CharacterClassConfig>
|
||||||
|
);
|
||||||
|
|
||||||
|
export function getCharacterClassIcon(
|
||||||
|
characterClass: CharacterClass | null | undefined
|
||||||
|
): string {
|
||||||
|
if (!characterClass) return "";
|
||||||
|
return CHARACTER_CLASS_MAP[characterClass]?.icon || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCharacterClassName(
|
||||||
|
characterClass: CharacterClass | null | undefined
|
||||||
|
): string {
|
||||||
|
if (!characterClass) return "";
|
||||||
|
return CHARACTER_CLASS_MAP[characterClass]?.name || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCharacterClassConfig(
|
||||||
|
characterClass: CharacterClass | null | undefined
|
||||||
|
): CharacterClassConfig | null {
|
||||||
|
if (!characterClass) return null;
|
||||||
|
return CHARACTER_CLASS_MAP[characterClass] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isValidCharacterClass(
|
||||||
|
characterClass: string | null | undefined
|
||||||
|
): characterClass is CharacterClass {
|
||||||
|
if (!characterClass) return false;
|
||||||
|
return characterClass in CHARACTER_CLASS_MAP;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { prisma } from "../database";
|
import { prisma } from "../database";
|
||||||
import type { User, Role, Prisma } from "@/prisma/generated/prisma/client";
|
import type { User, Role, Prisma, CharacterClass } from "@/prisma/generated/prisma/client";
|
||||||
import { NotFoundError } from "../errors";
|
import { NotFoundError } from "../errors";
|
||||||
import { userService } from "./user.service";
|
import { userService } from "./user.service";
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ export interface LeaderboardEntry {
|
|||||||
level: number;
|
level: number;
|
||||||
avatar: string | null;
|
avatar: string | null;
|
||||||
bio: string | null;
|
bio: string | null;
|
||||||
characterClass: string | null;
|
characterClass: CharacterClass | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,20 +6,7 @@ import type {
|
|||||||
Prisma,
|
Prisma,
|
||||||
} from "@/prisma/generated/prisma/client";
|
} from "@/prisma/generated/prisma/client";
|
||||||
import { ValidationError, NotFoundError, ConflictError } from "../errors";
|
import { ValidationError, NotFoundError, ConflictError } from "../errors";
|
||||||
|
import { isValidCharacterClass } from "@/lib/character-classes";
|
||||||
// Constantes de validation
|
|
||||||
const VALID_CHARACTER_CLASSES = [
|
|
||||||
"WARRIOR",
|
|
||||||
"MAGE",
|
|
||||||
"ROGUE",
|
|
||||||
"RANGER",
|
|
||||||
"PALADIN",
|
|
||||||
"ENGINEER",
|
|
||||||
"MERCHANT",
|
|
||||||
"SCHOLAR",
|
|
||||||
"BERSERKER",
|
|
||||||
"NECROMANCER",
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
const USERNAME_MIN_LENGTH = 3;
|
const USERNAME_MIN_LENGTH = 3;
|
||||||
const USERNAME_MAX_LENGTH = 20;
|
const USERNAME_MAX_LENGTH = 20;
|
||||||
@@ -305,10 +292,7 @@ export class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validation du characterClass
|
// Validation du characterClass
|
||||||
if (
|
if (data.characterClass && !isValidCharacterClass(data.characterClass)) {
|
||||||
data.characterClass &&
|
|
||||||
!VALID_CHARACTER_CLASSES.includes(data.characterClass as CharacterClass)
|
|
||||||
) {
|
|
||||||
throw new ValidationError(
|
throw new ValidationError(
|
||||||
"Classe de personnage invalide",
|
"Classe de personnage invalide",
|
||||||
"characterClass"
|
"characterClass"
|
||||||
@@ -402,7 +386,7 @@ export class UserService {
|
|||||||
if (
|
if (
|
||||||
data.characterClass !== undefined &&
|
data.characterClass !== undefined &&
|
||||||
data.characterClass !== null &&
|
data.characterClass !== null &&
|
||||||
!VALID_CHARACTER_CLASSES.includes(data.characterClass as CharacterClass)
|
!isValidCharacterClass(data.characterClass)
|
||||||
) {
|
) {
|
||||||
throw new ValidationError(
|
throw new ValidationError(
|
||||||
"Classe de personnage invalide",
|
"Classe de personnage invalide",
|
||||||
|
|||||||
Reference in New Issue
Block a user