Files
elysium/apps/web/src/components/game/GameLayout.tsx
T
hikari 924b9f541d feat: add character sheet panel with guild info
Adds pronouns, guildName, and guildDescription fields to the player
profile. A new Character tab provides an in-game view/edit panel for
character and guild lore.

Closes #16
2026-03-07 13:49:04 -08:00

143 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from "react";
import { useGame } from "../../context/GameContext.js";
import { ResourceBar } from "../ui/ResourceBar.js";
import { AboutPanel } from "./AboutPanel.js";
import { AchievementPanel } from "./AchievementPanel.js";
import { AchievementToast } from "./AchievementToast.js";
import { AdventurerPanel } from "./AdventurerPanel.js";
import { BattleModal } from "./BattleModal.js";
import { BossPanel } from "./BossPanel.js";
import { ClickArea } from "./ClickArea.js";
import { CodexPanel } from "./CodexPanel.js";
import { CodexToast } from "./CodexToast.js";
import { EditProfileModal } from "./EditProfileModal.js";
import { EquipmentPanel } from "./EquipmentPanel.js";
import { OfflineModal } from "./OfflineModal.js";
import { PrestigePanel } from "./PrestigePanel.js";
import { ApotheosisPanel } from "./ApotheosisPanel.js";
import { TranscendencePanel } from "./TranscendencePanel.js";
import { QuestPanel } from "./QuestPanel.js";
import { StatisticsPanel } from "./StatisticsPanel.js";
import { UpgradePanel } from "./UpgradePanel.js";
import { DailyChallengePanel } from "./DailyChallengePanel.js";
import { ExplorationPanel } from "./ExplorationPanel.js";
import { CharacterSheetPanel } from "./CharacterSheetPanel.js";
import { CraftingPanel } from "./CraftingPanel.js";
type Tab = "adventurers" | "upgrades" | "quests" | "bosses" | "equipment" | "achievements" | "prestige" | "transcendence" | "apotheosis" | "statistics" | "daily" | "codex" | "about" | "exploration" | "crafting" | "character";
const BASE_TABS: { id: Tab; label: string }[] = [
{ id: "adventurers", label: "βš”οΈ Adventurers" },
{ id: "upgrades", label: "πŸ”§ Upgrades" },
{ id: "quests", label: "πŸ“œ Quests" },
{ id: "bosses", label: "πŸ‘Ή Bosses" },
{ id: "equipment", label: "πŸ—‘οΈ Equipment" },
{ id: "exploration", label: "πŸ—ΊοΈ Exploration" },
{ id: "crafting", label: "βš—οΈ Crafting" },
{ id: "daily", label: "πŸ“… Daily" },
{ id: "prestige", label: "⭐ Prestige" },
{ id: "transcendence", label: "🌌 Transcendence" },
{ id: "apotheosis", label: "✨ Apotheosis" },
{ id: "statistics", label: "πŸ“Š Statistics" },
{ id: "character", label: "πŸ“‹ Character" },
{ id: "achievements", label: "πŸ† Achievements" },
{ id: "codex", label: "πŸ“– Codex" },
{ id: "about", label: "ℹ️ About" },
];
export const GameLayout = (): React.JSX.Element => {
const { state, isLoading, error, battleResult, dismissBattle, lastSavedAt, isSyncing, forceSync, newCodexEntryIds } = useGame();
const [activeTab, setActiveTab] = useState<Tab>("adventurers");
const [editingProfile, setEditingProfile] = useState(false);
if (isLoading) {
return (
<div className="loading-screen">
<p>Loading your adventure...</p>
</div>
);
}
if (error) {
return (
<div className="error-screen">
<p>Error: {error}</p>
</div>
);
}
if (!state) return <div className="loading-screen"><p>Loading...</p></div>;
const profileUrl = `/profile/${state.player.discordId}`;
return (
<div className="game-layout">
<ResourceBar
resources={state.resources}
runestones={state.prestige.runestones}
prestigeCount={state.prestige.count}
transcendenceCount={state.transcendence?.count ?? 0}
apotheosisCount={state.apotheosis?.count ?? 0}
profileUrl={profileUrl}
onEditProfile={() => { setEditingProfile(true); }}
lastSavedAt={lastSavedAt}
isSyncing={isSyncing}
onForceSync={forceSync}
/>
<OfflineModal />
<AchievementToast />
<CodexToast />
{battleResult && (
<BattleModal battle={battleResult} onDismiss={dismissBattle} />
)}
{editingProfile && (
<EditProfileModal onClose={() => { setEditingProfile(false); }} />
)}
<div className="game-main">
<aside className="game-sidebar">
<ClickArea />
<p className="game-copyright">Β© NHCarrigan</p>
</aside>
<main className="game-content">
<nav className="tab-bar">
{BASE_TABS.map((tab) => (
<button
key={tab.id}
className={`tab-button ${activeTab === tab.id ? "active" : ""}`}
onClick={() => { setActiveTab(tab.id); }}
type="button"
>
{tab.label}
{tab.id === "codex" && newCodexEntryIds.length > 0 && (
<span className="tab-badge">{newCodexEntryIds.length}</span>
)}
</button>
))}
</nav>
<div className="tab-content">
{activeTab === "adventurers" && <AdventurerPanel />}
{activeTab === "upgrades" && <UpgradePanel />}
{activeTab === "quests" && <QuestPanel />}
{activeTab === "bosses" && <BossPanel />}
{activeTab === "equipment" && <EquipmentPanel />}
{activeTab === "achievements" && <AchievementPanel />}
{activeTab === "prestige" && <PrestigePanel />}
{activeTab === "transcendence" && <TranscendencePanel />}
{activeTab === "apotheosis" && <ApotheosisPanel />}
{activeTab === "exploration" && <ExplorationPanel />}
{activeTab === "crafting" && <CraftingPanel />}
{activeTab === "statistics" && <StatisticsPanel />}
{activeTab === "daily" && <DailyChallengePanel />}
{activeTab === "character" && <CharacterSheetPanel />}
{activeTab === "codex" && <CodexPanel />}
{activeTab === "about" && <AboutPanel />}
</div>
</main>
</div>
</div>
);
};