generated from nhcarrigan/template
feat: initial elysium idle game prototype
Sets up the full monorepo with pnpm workspaces. Includes shared types package, Hono API with Discord OAuth/JWT auth, Prisma v6 + MongoDB Atlas, and React + Vite frontend with game loop, five tabs, and Discord-linked save/load.
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import { useState } from "react";
|
||||
import { prestige } from "../../api/client.js";
|
||||
import { useGame } from "../../context/GameContext.js";
|
||||
|
||||
const PRESTIGE_THRESHOLD = 1_000_000;
|
||||
|
||||
export const PrestigePanel = (): React.JSX.Element => {
|
||||
const { state, reload } = useGame();
|
||||
const [characterName, setCharacterName] = useState("");
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const [result, setResult] = useState<{ runestones: number; count: number } | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
if (!state) return <section className="panel"><p>Loading...</p></section>;
|
||||
|
||||
const isEligible = state.player.totalGoldEarned >= PRESTIGE_THRESHOLD;
|
||||
|
||||
const handlePrestige = async (): Promise<void> => {
|
||||
if (!characterName.trim()) return;
|
||||
setIsPending(true);
|
||||
setError(null);
|
||||
try {
|
||||
const data = await prestige({ characterName: characterName.trim() });
|
||||
setResult({ runestones: data.runestones, count: data.newPrestigeCount });
|
||||
await reload();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Prestige failed");
|
||||
} finally {
|
||||
setIsPending(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="panel prestige-panel">
|
||||
<h2>⭐ Prestige</h2>
|
||||
<p>
|
||||
Prestige resets your progress but grants <strong>Runestones</strong> — permanent
|
||||
currency used for powerful upgrades. Each prestige also increases your global
|
||||
production multiplier by 10%.
|
||||
</p>
|
||||
|
||||
<div className="prestige-status">
|
||||
<p>
|
||||
Total gold earned:{" "}
|
||||
<strong>{state.player.totalGoldEarned.toLocaleString()}</strong>
|
||||
</p>
|
||||
<p>
|
||||
Required: <strong>{PRESTIGE_THRESHOLD.toLocaleString()}</strong>
|
||||
</p>
|
||||
<p>Current prestige count: <strong>{state.prestige.count}</strong></p>
|
||||
<p>
|
||||
Production multiplier:{" "}
|
||||
<strong>×{state.prestige.productionMultiplier.toFixed(1)}</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{isEligible ? (
|
||||
<div className="prestige-form">
|
||||
<p>You are ready to prestige! Choose your new character name:</p>
|
||||
<input
|
||||
type="text"
|
||||
value={characterName}
|
||||
onChange={(e) => { setCharacterName(e.target.value); }}
|
||||
placeholder="Character name..."
|
||||
maxLength={32}
|
||||
disabled={isPending}
|
||||
/>
|
||||
<button
|
||||
className="prestige-button"
|
||||
onClick={() => { void handlePrestige(); }}
|
||||
disabled={isPending || !characterName.trim()}
|
||||
type="button"
|
||||
>
|
||||
{isPending ? "Ascending..." : "✨ Ascend"}
|
||||
</button>
|
||||
{error && <p className="error">{error}</p>}
|
||||
{result && (
|
||||
<p className="success">
|
||||
Ascended to Prestige {result.count}! Earned {result.runestones} Runestones.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<p className="prestige-locked">
|
||||
Earn {(PRESTIGE_THRESHOLD - state.player.totalGoldEarned).toLocaleString()} more
|
||||
gold to unlock prestige.
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user