refactor: types frontend alignés backend — zéro as any, monstres triés par level
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 33s
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 33s
types.ts: rewrite complet — Character, Monster, CombatResult, CombatLog alignés sur les champs réels du backend. Plus de mapping approximatif. CombatPage: réécriture propre — monstres triés par level (appropriés en haut, trop forts en bas avec opacity + warning), historique avec vrais noms de monstres et valeurs XP/or, level up affiché dans le résultat. Cleanup: 0 occurrence de "as any" dans tout le frontend.
This commit is contained in:
@@ -7,6 +7,7 @@ export interface User {
|
|||||||
|
|
||||||
export interface Character {
|
export interface Character {
|
||||||
id: string;
|
id: string;
|
||||||
|
userId: string;
|
||||||
name: string;
|
name: string;
|
||||||
level: number;
|
level: number;
|
||||||
xp: number;
|
xp: number;
|
||||||
@@ -18,21 +19,23 @@ export interface Character {
|
|||||||
vitalite: number;
|
vitalite: number;
|
||||||
hpCurrent: number;
|
hpCurrent: number;
|
||||||
hpMax: number;
|
hpMax: number;
|
||||||
endurance: number;
|
enduranceSaved: number;
|
||||||
enduranceCurrent: number; // calculé à la lecture (backend field)
|
lastEnduranceTs: string;
|
||||||
enduranceMax: number;
|
enduranceMax: number;
|
||||||
|
enduranceCurrent: number;
|
||||||
statPoints: number;
|
statPoints: number;
|
||||||
xpToNextLevel: number;
|
|
||||||
activeTitle: string | null;
|
activeTitle: string | null;
|
||||||
totalGoldEarned: number;
|
totalGoldEarned: number;
|
||||||
|
xpToNextLevel: number;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Monster {
|
export interface Monster {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
levelMin: number;
|
minLevel: number;
|
||||||
levelMax: number;
|
maxLevel: number;
|
||||||
hp: number;
|
hp: number;
|
||||||
attack: number;
|
attack: number;
|
||||||
defense: number;
|
defense: number;
|
||||||
@@ -40,37 +43,57 @@ export interface Monster {
|
|||||||
xpReward: number;
|
xpReward: number;
|
||||||
goldMin: number;
|
goldMin: number;
|
||||||
goldMax: number;
|
goldMax: number;
|
||||||
|
dropMaterialId: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CombatRound {
|
export interface CombatRound {
|
||||||
round: number;
|
round: number;
|
||||||
playerDamage: number;
|
playerAttack: { damage: number; isCrit: boolean; isDodged: boolean; log: string };
|
||||||
playerCrit: boolean;
|
monsterAttack: { damage: number; isCrit: boolean; isDodged: boolean; log: string };
|
||||||
monsterDodged: boolean;
|
|
||||||
monsterDamage: number;
|
|
||||||
playerDodged: boolean;
|
|
||||||
playerHp: number;
|
playerHp: number;
|
||||||
monsterHp: number;
|
monsterHp: number;
|
||||||
log: string[];
|
log: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CombatRewards {
|
||||||
|
xp: number;
|
||||||
|
gold: number;
|
||||||
|
goldLost: number;
|
||||||
|
levelUp: boolean;
|
||||||
|
newLevel: number;
|
||||||
|
statPointsGained: number;
|
||||||
|
loot: { name: string; quantity: number } | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CombatCharacterState {
|
||||||
|
level: number;
|
||||||
|
xp: number;
|
||||||
|
xpToNextLevel: number;
|
||||||
|
gold: number;
|
||||||
|
hpCurrent: number;
|
||||||
|
hpMax: number;
|
||||||
|
enduranceCurrent: number;
|
||||||
|
enduranceMax: number;
|
||||||
|
statPoints: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CombatResult {
|
export interface CombatResult {
|
||||||
winner: 'player' | 'monster';
|
winner: 'player' | 'monster';
|
||||||
rounds: CombatRound[];
|
rounds: CombatRound[];
|
||||||
xpGained?: number;
|
summary: string;
|
||||||
goldGained?: number;
|
rewards: CombatRewards;
|
||||||
enduranceCost: number;
|
character: CombatCharacterState;
|
||||||
loot?: { material: Material; quantity: number } | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CombatLog {
|
export interface CombatLog {
|
||||||
id: string;
|
id: string;
|
||||||
monsterId: string;
|
|
||||||
monsterName?: string;
|
|
||||||
winner: 'player' | 'monster';
|
winner: 'player' | 'monster';
|
||||||
xpGained: number;
|
totalRounds: number;
|
||||||
goldGained: number;
|
xpEarned: number;
|
||||||
|
goldEarned: number;
|
||||||
|
levelUp: boolean;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
monster: { id: string; name: string; minLevel: number; maxLevel: number };
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Rarity = 'common' | 'rare' | 'epic' | 'legendary';
|
export type Rarity = 'common' | 'rare' | 'epic' | 'legendary';
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ export function HudBar() {
|
|||||||
|
|
||||||
if (!char) return null;
|
if (!char) return null;
|
||||||
|
|
||||||
const endurance = (char as any).enduranceCurrent ?? (char as any).endurance ?? 0;
|
const endurance = char.enduranceCurrent;
|
||||||
const xpNext = (char as any).xpToNextLevel ?? Math.round(100 * Math.pow(char.level, 1.5));
|
const xpNext = char.xpToNextLevel;
|
||||||
const questCount = activeQuests?.filter((pq: any) => pq.status === 'active').length ?? 0;
|
const questCount = activeQuests?.filter((pq: any) => pq.status === 'active').length ?? 0;
|
||||||
const questReady = activeQuests?.filter((pq: any) => pq.status === 'completed').length ?? 0;
|
const questReady = activeQuests?.filter((pq: any) => pq.status === 'completed').length ?? 0;
|
||||||
|
|
||||||
@@ -88,11 +88,11 @@ export function HudBar() {
|
|||||||
<span style={{ color: endurance < 5 ? '#e84040' : '#6b7a99' }}>
|
<span style={{ color: endurance < 5 ? '#e84040' : '#6b7a99' }}>
|
||||||
{endurance}/{char.enduranceMax}
|
{endurance}/{char.enduranceMax}
|
||||||
</span>
|
</span>
|
||||||
{(char as any).lastEnduranceTs && (
|
{char.lastEnduranceTs && (
|
||||||
<RegenTimer
|
<RegenTimer
|
||||||
endurance={endurance}
|
endurance={endurance}
|
||||||
enduranceMax={char.enduranceMax}
|
enduranceMax={char.enduranceMax}
|
||||||
lastEnduranceTs={(char as any).lastEnduranceTs}
|
lastEnduranceTs={char.lastEnduranceTs}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { combatApi, characterApi } from '../api/endpoints';
|
import { combatApi, characterApi } from '../api/endpoints';
|
||||||
import type { Monster, CombatResult } from '../api/types';
|
import type { Monster, CombatResult, CombatLog } from '../api/types';
|
||||||
import { Swords, Trophy, Skull, Clock, Zap } from 'lucide-react';
|
import { Swords, Trophy, Skull, Clock, Zap } from 'lucide-react';
|
||||||
|
|
||||||
const COMBAT_COST = 5;
|
const COMBAT_COST = 5;
|
||||||
@@ -12,16 +12,21 @@ const ATTACK_TYPES = [
|
|||||||
{ id: 'magic', label: 'Magie', emoji: '✨', stat: 'Intelligence × 1.5' },
|
{ id: 'magic', label: 'Magie', emoji: '✨', stat: 'Intelligence × 1.5' },
|
||||||
];
|
];
|
||||||
|
|
||||||
function MonsterCard({ m, selected, onSelect }: { m: Monster; selected: boolean; onSelect: () => void }) {
|
function MonsterCard({ m, selected, onSelect, playerLevel }: { m: Monster; selected: boolean; onSelect: () => void; playerLevel: number }) {
|
||||||
|
const tooHard = m.minLevel > playerLevel + 2;
|
||||||
|
const tooEasy = m.maxLevel < playerLevel - 3;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`card card-hover ${selected ? 'card-gold' : ''}`}
|
className={`card card-hover ${selected ? 'card-gold' : ''}`}
|
||||||
onClick={onSelect}
|
onClick={onSelect}
|
||||||
style={{ cursor: 'pointer', transition: 'all 0.15s' }}
|
style={{ cursor: 'pointer', transition: 'all 0.15s', opacity: tooHard ? 0.4 : 1 }}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 6 }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 6 }}>
|
||||||
<span style={{ fontWeight: 700, fontSize: 14, color: selected ? '#f4c94e' : '#dce4f0' }}>{m.name}</span>
|
<span style={{ fontWeight: 700, fontSize: 14, color: selected ? '#f4c94e' : '#dce4f0' }}>{m.name}</span>
|
||||||
<span className="badge badge-red" style={{ fontSize: 10 }}>Niv. {(m as any).minLevel ?? m.levelMin}–{(m as any).maxLevel ?? m.levelMax}</span>
|
<span className={tooHard ? 'badge badge-red' : tooEasy ? 'badge' : 'badge badge-green'} style={{ fontSize: 10 }}>
|
||||||
|
Niv. {m.minLevel}–{m.maxLevel}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: 12, fontSize: 12, color: '#6b7a99' }}>
|
<div style={{ display: 'flex', gap: 12, fontSize: 12, color: '#6b7a99' }}>
|
||||||
<span>❤️ {m.hp}</span>
|
<span>❤️ {m.hp}</span>
|
||||||
@@ -30,49 +35,49 @@ function MonsterCard({ m, selected, onSelect }: { m: Monster; selected: boolean;
|
|||||||
<span>⭐ {m.xpReward} XP</span>
|
<span>⭐ {m.xpReward} XP</span>
|
||||||
<span>💰 {m.goldMin}–{m.goldMax}</span>
|
<span>💰 {m.goldMin}–{m.goldMax}</span>
|
||||||
</div>
|
</div>
|
||||||
|
{tooHard && <div style={{ fontSize: 10, color: '#e84040', marginTop: 4 }}>Niveau trop élevé</div>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CombatLog({ result }: { result: CombatResult }) {
|
function CombatLogView({ result }: { result: CombatResult }) {
|
||||||
const won = result.winner === 'player';
|
const won = result.winner === 'player';
|
||||||
return (
|
return (
|
||||||
<div className="card" style={{ marginTop: '1rem' }}>
|
<div className="card" style={{ marginTop: '1rem' }}>
|
||||||
{/* Résultat */}
|
|
||||||
<div style={{ textAlign: 'center', padding: '0.75rem 0', marginBottom: '0.75rem', borderBottom: '1px solid #2a3448' }}>
|
<div style={{ textAlign: 'center', padding: '0.75rem 0', marginBottom: '0.75rem', borderBottom: '1px solid #2a3448' }}>
|
||||||
{won
|
{won
|
||||||
? <div style={{ color: '#3ddc84', fontWeight: 800, fontSize: 18 }}>
|
? <div style={{ color: '#3ddc84', fontWeight: 800, fontSize: 18 }}>
|
||||||
<Trophy size={20} style={{ display: 'inline', marginRight: 8 }} />
|
<Trophy size={20} style={{ display: 'inline', marginRight: 8 }} />
|
||||||
Victoire ! +{(result as any).rewards?.xp ?? result.xpGained} XP +{(result as any).rewards?.gold ?? result.goldGained} or
|
Victoire ! +{result.rewards.xp} XP +{result.rewards.gold} or
|
||||||
</div>
|
</div>
|
||||||
: <div style={{ color: '#e84040', fontWeight: 800, fontSize: 18 }}>
|
: <div style={{ color: '#e84040', fontWeight: 800, fontSize: 18 }}>
|
||||||
<Skull size={20} style={{ display: 'inline', marginRight: 8 }} />
|
<Skull size={20} style={{ display: 'inline', marginRight: 8 }} />
|
||||||
Défaite… −50 endurance
|
Défaite… Retour à l'auberge
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{((result as any).rewards?.loot ?? result.loot) && (
|
{result.rewards.loot && (
|
||||||
<div style={{ fontSize: 13, color: '#f4c94e', marginTop: 4 }}>
|
<div style={{ fontSize: 13, color: '#f4c94e', marginTop: 4 }}>
|
||||||
🎁 Loot obtenu !
|
🎁 Loot : {result.rewards.loot.name} ×{result.rewards.loot.quantity}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(result as any).rewards?.levelUp && (
|
{result.rewards.levelUp && (
|
||||||
<div style={{ fontSize: 13, color: '#a78bfa', marginTop: 4 }}>
|
<div style={{ fontSize: 13, color: '#a78bfa', marginTop: 4 }}>
|
||||||
🎉 LEVEL UP ! Niveau {(result as any).rewards.newLevel} — +{(result as any).rewards.statPointsGained} points de stats
|
🎉 LEVEL UP ! Niveau {result.rewards.newLevel} — +{result.rewards.statPointsGained} points de stats
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Log de combat */}
|
|
||||||
<p style={{ margin: '0 0 6px', fontSize: 12, fontWeight: 700, color: '#6b7a99' }}>
|
<p style={{ margin: '0 0 6px', fontSize: 12, fontWeight: 700, color: '#6b7a99' }}>
|
||||||
Log — {result.rounds.length} tour{result.rounds.length > 1 ? 's' : ''}
|
Log — {result.rounds.length} tour{result.rounds.length > 1 ? 's' : ''}
|
||||||
</p>
|
</p>
|
||||||
<div className="combat-log">
|
<div className="combat-log">
|
||||||
{result.rounds.flatMap(r =>
|
{result.rounds.flatMap(r =>
|
||||||
r.log.map((line, i) => {
|
r.log.map((line, i) => {
|
||||||
const cls = line.includes('frappe') && !line.includes('Monstre') ? 'log-player'
|
const cls = line.includes('CRITIQUE') ? 'log-crit'
|
||||||
: line.includes('Monstre') || line.includes('frappe') ? 'log-monster'
|
: line.includes('esquive') ? 'log-crit'
|
||||||
: line.includes('CRITIQUE') ? 'log-crit'
|
: line.includes('HP') ? 'log-system'
|
||||||
: 'log-system';
|
: i === 0 ? 'log-player'
|
||||||
|
: 'log-monster';
|
||||||
return <div key={`${r.round}-${i}`} className={cls}>[T{r.round}] {line}</div>;
|
return <div key={`${r.round}-${i}`} className={cls}>[T{r.round}] {line}</div>;
|
||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
@@ -85,6 +90,19 @@ function CombatLog({ result }: { result: CombatResult }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function HistoryEntry({ h }: { h: CombatLog }) {
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12, padding: '3px 0', borderBottom: '1px solid #1e2535' }}>
|
||||||
|
<span style={{ color: h.winner === 'player' ? '#3ddc84' : '#e84040' }}>
|
||||||
|
{h.winner === 'player' ? '✓' : '✗'} {h.monster.name}
|
||||||
|
</span>
|
||||||
|
<span style={{ color: '#6b7a99' }}>
|
||||||
|
{h.winner === 'player' ? `+${h.xpEarned}xp +${h.goldEarned}or` : `${h.totalRounds} tours`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function CombatPage() {
|
export function CombatPage() {
|
||||||
const qc = useQueryClient();
|
const qc = useQueryClient();
|
||||||
const [selectedMonster, setSelectedMonster] = useState<Monster | null>(null);
|
const [selectedMonster, setSelectedMonster] = useState<Monster | null>(null);
|
||||||
@@ -92,7 +110,8 @@ export function CombatPage() {
|
|||||||
const [lastResult, setLastResult] = useState<CombatResult | null>(null);
|
const [lastResult, setLastResult] = useState<CombatResult | null>(null);
|
||||||
|
|
||||||
const { data: char } = useQuery({ queryKey: ['character'], queryFn: characterApi.me });
|
const { data: char } = useQuery({ queryKey: ['character'], queryFn: characterApi.me });
|
||||||
const endurance = (char as any)?.enduranceCurrent ?? (char as any)?.endurance ?? 0;
|
const endurance = char?.enduranceCurrent ?? 0;
|
||||||
|
const playerLevel = char?.level ?? 1;
|
||||||
const canFight = endurance >= COMBAT_COST;
|
const canFight = endurance >= COMBAT_COST;
|
||||||
|
|
||||||
const { data: monsters, isLoading } = useQuery({
|
const { data: monsters, isLoading } = useQuery({
|
||||||
@@ -111,11 +130,20 @@ export function CombatPage() {
|
|||||||
setLastResult(result);
|
setLastResult(result);
|
||||||
qc.invalidateQueries({ queryKey: ['character'] });
|
qc.invalidateQueries({ queryKey: ['character'] });
|
||||||
qc.invalidateQueries({ queryKey: ['combatHistory'] });
|
qc.invalidateQueries({ queryKey: ['combatHistory'] });
|
||||||
|
qc.invalidateQueries({ queryKey: ['questsActive'] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isLoading) return <div style={{ padding: '2rem', color: '#6b7a99' }}>Chargement des monstres…</div>;
|
if (isLoading) return <div style={{ padding: '2rem', color: '#6b7a99' }}>Chargement des monstres…</div>;
|
||||||
|
|
||||||
|
// Trier : monstres appropriés en haut, trop forts en bas
|
||||||
|
const sorted = [...(monsters ?? [])].sort((a, b) => {
|
||||||
|
const aOk = a.minLevel <= playerLevel + 2 ? 0 : 1;
|
||||||
|
const bOk = b.minLevel <= playerLevel + 2 ? 0 : 1;
|
||||||
|
if (aOk !== bOk) return aOk - bOk;
|
||||||
|
return a.minLevel - b.minLevel;
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2 style={{ margin: '0 0 1rem', color: '#f4c94e', fontSize: 20 }}>⚔️ Combat</h2>
|
<h2 style={{ margin: '0 0 1rem', color: '#f4c94e', fontSize: 20 }}>⚔️ Combat</h2>
|
||||||
@@ -127,12 +155,13 @@ export function CombatPage() {
|
|||||||
Adversaire
|
Adversaire
|
||||||
</p>
|
</p>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||||
{monsters?.map(m => (
|
{sorted.map(m => (
|
||||||
<MonsterCard
|
<MonsterCard
|
||||||
key={m.id}
|
key={m.id}
|
||||||
m={m}
|
m={m}
|
||||||
selected={selectedMonster?.id === m.id}
|
selected={selectedMonster?.id === m.id}
|
||||||
onSelect={() => setSelectedMonster(m)}
|
onSelect={() => setSelectedMonster(m)}
|
||||||
|
playerLevel={playerLevel}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -150,7 +179,7 @@ export function CombatPage() {
|
|||||||
key={a.id}
|
key={a.id}
|
||||||
className={`card card-hover ${attackType === a.id ? 'card-gold' : ''}`}
|
className={`card card-hover ${attackType === a.id ? 'card-gold' : ''}`}
|
||||||
onClick={() => setAttackType(a.id)}
|
onClick={() => setAttackType(a.id)}
|
||||||
style={{ display: 'flex', alignItems: 'center', gap: 10 }}
|
style={{ display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer' }}
|
||||||
>
|
>
|
||||||
<span style={{ fontSize: 18 }}>{a.emoji}</span>
|
<span style={{ fontSize: 18 }}>{a.emoji}</span>
|
||||||
<div>
|
<div>
|
||||||
@@ -164,7 +193,7 @@ export function CombatPage() {
|
|||||||
{/* Coût endurance */}
|
{/* Coût endurance */}
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6, marginBottom: 6, fontSize: 12, color: canFight ? '#5ba4f5' : '#e84040' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6, marginBottom: 6, fontSize: 12, color: canFight ? '#5ba4f5' : '#e84040' }}>
|
||||||
<Zap size={12} /> Coût : {COMBAT_COST} endurance — Disponible : {endurance}
|
<Zap size={12} /> Coût : {COMBAT_COST} endurance — Disponible : {endurance}
|
||||||
{canFight && <span style={{ color: '#6b7a99' }}>({Math.floor(endurance / COMBAT_COST)} combats possibles)</span>}
|
{canFight && <span style={{ color: '#6b7a99' }}>({Math.floor(endurance / COMBAT_COST)} combats)</span>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bouton combattre */}
|
{/* Bouton combattre */}
|
||||||
@@ -192,14 +221,7 @@ export function CombatPage() {
|
|||||||
<Clock size={11} /> Historique récent
|
<Clock size={11} /> Historique récent
|
||||||
</p>
|
</p>
|
||||||
<div className="card" style={{ padding: '0.75rem' }}>
|
<div className="card" style={{ padding: '0.75rem' }}>
|
||||||
{history.slice(0, 5).map(h => (
|
{history.slice(0, 5).map(h => <HistoryEntry key={h.id} h={h} />)}
|
||||||
<div key={h.id} style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12, padding: '3px 0', borderBottom: '1px solid #1e2535' }}>
|
|
||||||
<span style={{ color: h.winner === 'player' ? '#3ddc84' : '#e84040' }}>
|
|
||||||
{h.winner === 'player' ? '✓' : '✗'} {(h as any).monster?.name ?? h.monsterName ?? 'Monstre'}
|
|
||||||
</span>
|
|
||||||
<span style={{ color: '#6b7a99' }}>+{(h as any).xpEarned ?? h.xpGained}xp +{(h as any).goldEarned ?? h.goldGained}or</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -207,7 +229,7 @@ export function CombatPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Résultat du dernier combat */}
|
{/* Résultat du dernier combat */}
|
||||||
{lastResult && <CombatLog result={lastResult} />}
|
{lastResult && <CombatLogView result={lastResult} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,10 +150,10 @@ export function DashboardPage() {
|
|||||||
if (isLoading) return <div style={{ padding: '2rem', color: '#6b7a99' }}>Chargement…</div>;
|
if (isLoading) return <div style={{ padding: '2rem', color: '#6b7a99' }}>Chargement…</div>;
|
||||||
if (isError || !char) return <CreateCharacter />;
|
if (isError || !char) return <CreateCharacter />;
|
||||||
|
|
||||||
const xpNext = (char as any).xpToNextLevel ?? Math.round(100 * Math.pow(char.level, 1.5));
|
const xpNext = char.xpToNextLevel;
|
||||||
const statPoints = (char as any).statPoints ?? 0;
|
const statPoints = char.statPoints ?? 0;
|
||||||
const needsHeal = char.hpCurrent < char.hpMax;
|
const needsHeal = char.hpCurrent < char.hpMax;
|
||||||
const endurance = (char as any).enduranceCurrent ?? (char as any).endurance ?? 0;
|
const endurance = char.enduranceCurrent;
|
||||||
const REST_COST = 10;
|
const REST_COST = 10;
|
||||||
const COMBAT_COST = 5;
|
const COMBAT_COST = 5;
|
||||||
const FORGE_COST = 10;
|
const FORGE_COST = 10;
|
||||||
@@ -202,9 +202,9 @@ export function DashboardPage() {
|
|||||||
<span style={{ fontSize: 12, color: '#5ba4f5', display: 'flex', alignItems: 'center', gap: 4 }}>
|
<span style={{ fontSize: 12, color: '#5ba4f5', display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||||
<Zap size={11} /> Endurance
|
<Zap size={11} /> Endurance
|
||||||
</span>
|
</span>
|
||||||
<span style={{ fontSize: 11, color: '#6b7a99' }}>{char.enduranceCurrent ?? char.endurance} / {char.enduranceMax}</span>
|
<span style={{ fontSize: 11, color: '#6b7a99' }}>{endurance} / {char.enduranceMax}</span>
|
||||||
</div>
|
</div>
|
||||||
<Bar value={char.enduranceCurrent ?? char.endurance} max={char.enduranceMax} type="end" showValues={false} />
|
<Bar value={endurance} max={char.enduranceMax} type="end" showValues={false} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }}>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export function ForgePage() {
|
|||||||
const [lastResult, setLastResult] = useState<{ success: boolean; newLevel: number } | null>(null);
|
const [lastResult, setLastResult] = useState<{ success: boolean; newLevel: number } | null>(null);
|
||||||
|
|
||||||
const { data: char } = useQuery({ queryKey: ['character'], queryFn: characterApi.me });
|
const { data: char } = useQuery({ queryKey: ['character'], queryFn: characterApi.me });
|
||||||
const endurance = (char as any)?.enduranceCurrent ?? (char as any)?.endurance ?? 0;
|
const endurance = char?.enduranceCurrent ?? 0;
|
||||||
const gold = char?.gold ?? 0;
|
const gold = char?.gold ?? 0;
|
||||||
|
|
||||||
const { data: inventory, isLoading } = useQuery({
|
const { data: inventory, isLoading } = useQuery({
|
||||||
|
|||||||
Reference in New Issue
Block a user