Files
ClickerZ/Frontend/src/components/MilestonesPanel.tsx
Tetardtek 450d559216
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 18s
fix: guard claimedMilestones in MilestonesPanel component
2026-03-28 18:39:27 +01:00

91 lines
3.2 KiB
TypeScript

// MilestonesPanel.tsx — Paliers de prestige (Sprint 3)
// Progress bar vers le prochain milestone, claim button, preview reward
import { useGameStore } from "../store/useGameStore";
import { getClaimableMilestones, getNextMilestone } from "../core/economy";
import { PRESTIGE_MILESTONES } from "../data/prestigeMilestones";
export function MilestonesPanel() {
const state = useGameStore((s) => s.state);
const claim = useGameStore((s) => s.claimMilestone);
if (state.prestigeCount < 1) return null;
const claimable = getClaimableMilestones(state);
const nextMilestone = getNextMilestone(state);
const claimed = state.claimedMilestones ?? [];
const totalClaimed = claimed.length;
return (
<div className="gp">
<div className="flex justify-between items-center">
<span className="gp-title">Milestones</span>
<span className="gp-label">{totalClaimed}/{PRESTIGE_MILESTONES.length}</span>
</div>
{/* Claimable milestones */}
{claimable.length > 0 && (
<div className="flex flex-col gap-1.5">
{claimable.map((m) => (
<div key={m.id} className="gp-row gp-row--evolution border-purple-400/30!">
<div className="flex flex-col min-w-0">
<span className="gp-value text-[0.7rem]!">{m.name}</span>
<span className="gp-label">{m.reward.label}</span>
</div>
<button
onClick={() => claim(m.id)}
className="gp-btn gp-btn--buy"
>
Claim
</button>
</div>
))}
</div>
)}
{/* Progress vers le prochain milestone */}
{nextMilestone && (
<div className="flex flex-col gap-1">
<div className="flex justify-between">
<span className="gp-label">Prochain : {nextMilestone.name}</span>
<span className="gp-label">
{state.prestigeCount}/{nextMilestone.threshold}
</span>
</div>
<div className="gp-progress">
<div
className="gp-progress-fill bg-gradient-to-r from-purple-600 to-purple-400"
style={{
width: `${Math.min((state.prestigeCount / nextMilestone.threshold) * 100, 100).toFixed(1)}%`,
}}
/>
</div>
<span className="gp-label">{nextMilestone.reward.label}</span>
</div>
)}
{/* Tous les milestones réclamés */}
{!nextMilestone && claimable.length === 0 && (
<span className="gp-label text-center gp-accent-purple">
Tous les milestones reclames !
</span>
)}
{/* Liste compacte des milestones passés */}
{totalClaimed > 0 && claimable.length === 0 && (
<div className="flex flex-wrap gap-1 mt-1">
{PRESTIGE_MILESTONES.filter((m) => claimed.includes(m.id)).map((m) => (
<span
key={m.id}
className="gp-label text-[0.55rem]! px-1.5 py-0.5 rounded bg-purple-500/10 border border-purple-500/20"
title={`${m.name}${m.description}`}
>
{m.threshold}
</span>
))}
</div>
)}
</div>
);
}