generated from nhcarrigan/template
88 lines
3.1 KiB
TypeScript
88 lines
3.1 KiB
TypeScript
import type { Achievement } from "@elysium/types";
|
|
import { useState } from "react";
|
|
import { useGame } from "../../context/GameContext.js";
|
|
import { formatNumber } from "../../utils/format.js";
|
|
import { LockToggle } from "../ui/LockToggle.js";
|
|
|
|
const conditionDescription = (achievement: Achievement): string => {
|
|
const { condition } = achievement;
|
|
switch (condition.type) {
|
|
case "totalGoldEarned":
|
|
return `Earn ${formatNumber(condition.amount)} total gold`;
|
|
case "totalClicks":
|
|
return `Click ${formatNumber(condition.amount)} times`;
|
|
case "bossesDefeated":
|
|
return `Defeat ${condition.amount} boss${condition.amount > 1 ? "es" : ""}`;
|
|
case "questsCompleted":
|
|
return `Complete ${condition.amount} quest${condition.amount > 1 ? "s" : ""}`;
|
|
case "adventurerTotal":
|
|
return `Recruit ${formatNumber(condition.amount)} total adventurers`;
|
|
case "prestigeCount":
|
|
return `Prestige ${condition.amount} time${condition.amount > 1 ? "s" : ""}`;
|
|
case "equipmentOwned":
|
|
return `Own ${condition.amount} equipment item${condition.amount > 1 ? "s" : ""}`;
|
|
}
|
|
};
|
|
|
|
interface AchievementCardProps {
|
|
achievement: Achievement;
|
|
}
|
|
|
|
const AchievementCard = ({ achievement }: AchievementCardProps): React.JSX.Element => {
|
|
const isUnlocked = achievement.unlockedAt !== null;
|
|
|
|
return (
|
|
<div className={`achievement-card ${isUnlocked ? "unlocked" : "locked"}`}>
|
|
<div className="achievement-icon">{achievement.icon}</div>
|
|
<div className="achievement-info">
|
|
<h3>{achievement.name}</h3>
|
|
<p>{achievement.description}</p>
|
|
<p className="achievement-condition">{conditionDescription(achievement)}</p>
|
|
{achievement.reward?.crystals != null && (
|
|
<p className="achievement-reward">💎 +{achievement.reward.crystals} Crystals</p>
|
|
)}
|
|
</div>
|
|
<div className="achievement-status">
|
|
{isUnlocked ? (
|
|
<span className="achievement-unlocked-badge">✓ Unlocked</span>
|
|
) : (
|
|
<span className="achievement-locked-badge">🔒</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export const AchievementPanel = (): React.JSX.Element => {
|
|
const { state } = useGame();
|
|
const [showLocked, setShowLocked] = useState(true);
|
|
|
|
if (!state) return <section className="panel"><p>Loading...</p></section>;
|
|
|
|
const achievements = state.achievements ?? [];
|
|
const unlocked = achievements.filter((a) => a.unlockedAt !== null);
|
|
const locked = achievements.filter((a) => a.unlockedAt === null);
|
|
const visible = showLocked ? achievements : unlocked;
|
|
|
|
return (
|
|
<section className="panel achievement-panel">
|
|
<div className="panel-header">
|
|
<h2>Achievements</h2>
|
|
<LockToggle
|
|
lockedCount={locked.length}
|
|
showLocked={showLocked}
|
|
onToggle={() => { setShowLocked((v) => !v); }}
|
|
/>
|
|
</div>
|
|
<p className="achievement-progress">
|
|
{unlocked.length} / {achievements.length} unlocked
|
|
</p>
|
|
<div className="achievement-list">
|
|
{visible.map((achievement) => (
|
|
<AchievementCard key={achievement.id} achievement={achievement} />
|
|
))}
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|