refactor: extract sub-components from monster functions (issue #99)
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m21s

- GuardianPactsTab: extracted GuardianCard, PactHeaderSummary, TierFilter + 5 helper components into guardian-pacts-components.tsx
- SpireSummaryTab: extracted TopStatsRow, NextGuardianCard, GuardianRoster, GuardianRosterItem, FloorLegend
- PrestigeTab: extracted InsightSummary, MemoriesCard, PactsCard, ResetLoopSection
- GameStateDebug: extracted WarningBanner, DisplayOptions, GameResetSection, ManaDebugSection, TimeControlSection, QuickActionsSection
- EquipmentCrafter: extracted CraftingProgress, BlueprintCard, BlueprintList, MaterialCard, MaterialsInventory
- PactDebug: extracted GuardianPactRow, GuardianPactList
- GameStateDebugSection: extracted DisplayOptions, GameResetSection, ManaDebugSection, TimeControlSection, QuickActionsSection
- PactDebugSection: extracted GuardianPactRow
- SpireCombatPage: extracted useSpireStats hook
- page.tsx: extracted GrimoireTab to separate file, useGameDerivedStats hook, TabTriggers, LazyTab wrapper

All files now under 400 lines. Build passes. All 639 tests pass.
This commit is contained in:
2026-05-20 18:38:24 +02:00
parent 53b3a94725
commit ce084a61a3
15 changed files with 1765 additions and 1539 deletions
+77
View File
@@ -0,0 +1,77 @@
'use client';
import { useState, useEffect } from 'react';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Badge } from '@/components/ui/badge';
import { DebugName } from '@/components/game/debug/debug-context';
import { SPELLS_DEF } from '@/lib/game/constants';
import type { SpellDef } from '@/lib/game/types';
export function GrimoireTab() {
const [grimoireSpells, setGrimoireSpells] = useState<[string, SpellDef][]>([]);
const [loaded, setLoaded] = useState(false);
useEffect(() => {
if (typeof window !== 'undefined' && SPELLS_DEF) {
setGrimoireSpells(
Object.entries(SPELLS_DEF).filter((entry): entry is [string, SpellDef] => !!entry[1].grimoire)
);
}
setLoaded(true);
}, []);
if (!loaded) {
return <div className="p-4 text-center text-gray-400">Loading grimoire...</div>;
}
if (grimoireSpells.length === 0) {
return (
<div className="p-4 text-center text-gray-400">
No grimoire spells available yet. Defeat guardians to unlock spells.
</div>
);
}
const availablePages = Math.ceil(grimoireSpells.length / 12);
return (
<DebugName name="GrimoireTab">
<div className="space-y-4">
<div className="text-sm text-gray-400">
<p className="mb-2">A vast tome of arcane knowledge. Study carefully each spell costs insight to transcribe into your repertoire.</p>
<p>Available pages: {availablePages}. Spells in grimoire: {grimoireSpells.length}.</p>
</div>
<ScrollArea className="h-[600px] rounded border border-gray-700 p-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{grimoireSpells.map(([id, spell]) => (
<div
key={id}
className="p-4 bg-gray-800/50 rounded border border-gray-600 hover:border-gray-500 transition-colors"
>
<div className="flex items-start justify-between mb-2">
<span className="font-bold text-gray-100">{spell.name}</span>
<Badge variant="outline" className="border-gray-600">
{spell.elem}
</Badge>
</div>
{spell.desc && <p className="text-sm text-gray-400 mb-3">{spell.desc}</p>}
<div className="text-xs text-gray-500 space-y-1">
<div>Cost: {spell.cost.amount} {
spell.cost.type === 'element'
? spell.cost.element
: 'raw mana'
}</div>
<div>Power: {spell.dmg}</div>
{spell.effects && spell.effects.length > 0 && (
<div>Effects: {spell.effects.map(e => e.type).join(', ')}</div>
)}
</div>
</div>
))}
</div>
</ScrollArea>
</div>
</DebugName>
);
}