diff --git a/frontend/src/components/HudBar.tsx b/frontend/src/components/HudBar.tsx new file mode 100644 index 0000000..93011ce --- /dev/null +++ b/frontend/src/components/HudBar.tsx @@ -0,0 +1,128 @@ +import { useQuery } from '@tanstack/react-query'; +import { Link } from 'react-router-dom'; +import { characterApi, questApi } from '../api/endpoints'; +import { Heart, Zap, Star, Coins, Scroll, Clock } from 'lucide-react'; +import { useState, useEffect } from 'react'; + +function RegenTimer({ endurance, enduranceMax, lastEnduranceTs }: { endurance: number; enduranceMax: number; lastEnduranceTs: string }) { + const [now, setNow] = useState(Date.now()); + + useEffect(() => { + if (endurance >= enduranceMax) return; + const id = setInterval(() => setNow(Date.now()), 1000); + return () => clearInterval(id); + }, [endurance, enduranceMax]); + + if (endurance >= enduranceMax) return null; + + // Regen = 1pt every 3min = 180s + const elapsedMs = now - new Date(lastEnduranceTs).getTime(); + const elapsedInCycle = elapsedMs % (3 * 60 * 1000); + const remainingMs = 3 * 60 * 1000 - elapsedInCycle; + const remainingSec = Math.max(0, Math.floor(remainingMs / 1000)); + const min = Math.floor(remainingSec / 60); + const sec = remainingSec % 60; + + return ( + + + +1 dans {min}:{sec.toString().padStart(2, '0')} + + ); +} + +export function HudBar() { + const { data: char } = useQuery({ + queryKey: ['character'], + queryFn: characterApi.me, + refetchInterval: 30_000, // refresh every 30s for endurance updates + }); + + const { data: activeQuests } = useQuery({ + queryKey: ['questsActive'], + queryFn: questApi.active, + refetchInterval: 60_000, + }); + + if (!char) return null; + + const endurance = (char as any).enduranceCurrent ?? (char as any).endurance ?? 0; + const xpNext = (char as any).xpToNextLevel ?? Math.round(100 * Math.pow(char.level, 1.5)); + const questCount = activeQuests?.filter((pq: any) => pq.status === 'active').length ?? 0; + const questReady = activeQuests?.filter((pq: any) => pq.status === 'completed').length ?? 0; + + return ( +
+ {/* Name + Level */} + + 🐾 + {char.name} + Niv.{char.level} + + + | + + {/* HP */} + + + + {char.hpCurrent}/{char.hpMax} + + + + | + + {/* Endurance + timer */} + + + + {endurance}/{char.enduranceMax} + + {(char as any).lastEnduranceTs && ( + + )} + + + | + + {/* XP */} + + + {char.xp}/{xpNext} + + + | + + {/* Gold */} + + + {char.gold} + + + | + + {/* Quests */} + + 0 ? '#f4c94e' : '#6b7a99'} /> + {questCount} quĂȘte{questCount !== 1 ? 's' : ''} + {questReady > 0 && ( + ({questReady} prĂȘte{questReady > 1 ? 's' : ''} !) + )} + +
+ ); +} diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 42ee373..27b3f56 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -1,6 +1,7 @@ import { Link, useLocation } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; import { Swords, Package, Hammer, User, LogOut, Shield, Scroll } from 'lucide-react'; +import { HudBar } from './HudBar'; const NAV = [ { to: '/dashboard', icon: User, label: 'Personnage' }, @@ -44,6 +45,7 @@ export function Layout({ children }: { children: React.ReactNode }) { )} +
{/* Sidebar nav */}