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
@@ -6,6 +6,51 @@ import { Bug } from 'lucide-react';
import { usePrestigeStore, useManaStore, useUIStore, useGameStore } from '@/lib/game/stores';
import { GUARDIANS, ELEMENTS } from '@/lib/game/constants';
// ─── Guardian Pact Row ───────────────────────────────────────────────────────
function GuardianPactRow({ floor, isSigned, onForceSign, onRemove }: {
floor: number;
isSigned: boolean;
onForceSign: () => void;
onRemove: () => void;
}) {
const guardian = GUARDIANS[floor];
return (
<div
className={`p-2 rounded border flex items-center justify-between ${
isSigned ? 'border-green-600/50 bg-green-900/20' : 'border-gray-700'
}`}
style={{ borderColor: isSigned ? undefined : guardian.color, borderWidth: '1px' }}
>
<div>
<div className="text-sm font-semibold" style={{ color: guardian.color }}>
{guardian.name}
</div>
<div className="text-xs text-gray-400">
Floor {floor} | {guardian.pact}x multiplier
</div>
<div className="text-xs text-gray-500">
Element: {ELEMENTS[guardian.element]?.name || guardian.element}
</div>
</div>
<div className="flex gap-1">
{isSigned ? (
<Button size="sm" variant="destructive" onClick={onRemove} className="text-xs">
Remove
</Button>
) : (
<Button size="sm" variant="default" onClick={onForceSign} className="text-xs bg-amber-600 hover:bg-amber-700">
Force Sign
</Button>
)}
</div>
</div>
);
}
// ─── Main Component ───────────────────────────────────────────────────────────
export function PactDebugSection() {
const signedPacts = usePrestigeStore((s) => s.signedPacts);
const signedPactDetails = usePrestigeStore((s) => s.signedPactDetails);
@@ -99,53 +144,15 @@ export function PactDebugSection() {
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2">
{guardianFloors.map((floor) => {
const guardian = GUARDIANS[floor];
const isSigned = signedPacts.includes(floor);
return (
<div
key={floor}
className={`p-2 rounded border flex items-center justify-between ${
isSigned ? 'border-green-600/50 bg-green-900/20' : 'border-gray-700'
}`}
style={{ borderColor: isSigned ? undefined : guardian.color, borderWidth: '1px' }}
>
<div>
<div className="text-sm font-semibold" style={{ color: guardian.color }}>
{guardian.name}
</div>
<div className="text-xs text-gray-400">
Floor {floor} | {guardian.pact}x multiplier
</div>
<div className="text-xs text-gray-500">
Element: {ELEMENTS[guardian.element]?.name || guardian.element}
</div>
</div>
<div className="flex gap-1">
{isSigned ? (
<Button
size="sm"
variant="destructive"
onClick={() => removePactHandler(floor)}
className="text-xs"
>
Remove
</Button>
) : (
<Button
size="sm"
variant="default"
onClick={() => forcePact(floor)}
className="text-xs bg-amber-600 hover:bg-amber-700"
>
Force Sign
</Button>
)}
</div>
</div>
);
})}
{guardianFloors.map((floor) => (
<GuardianPactRow
key={floor}
floor={floor}
isSigned={signedPacts.includes(floor)}
onForceSign={() => forcePact(floor)}
onRemove={() => removePactHandler(floor)}
/>
))}
</div>
<div className="text-xs text-gray-400 pt-2 border-t border-gray-700">
@@ -158,4 +165,4 @@ export function PactDebugSection() {
);
}
PactDebugSection.displayName = "PactDebugSection";
PactDebugSection.displayName = 'PactDebugSection';