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
+84 -80
View File
@@ -6,48 +6,106 @@ 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>
);
}
// ─── Guardian Pact List ──────────────────────────────────────────────────────
function GuardianPactList({ signedPacts, onForceSign, onRemove }: {
signedPacts: number[];
onForceSign: (floor: number) => void;
onRemove: (floor: number) => void;
}) {
const guardianFloors = Object.keys(GUARDIANS || {}).map(Number).sort((a, b) => a - b);
return (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2">
{guardianFloors.map((floor) => (
<GuardianPactRow
key={floor}
floor={floor}
isSigned={signedPacts.includes(floor)}
onForceSign={() => onForceSign(floor)}
onRemove={() => onRemove(floor)}
/>
))}
</div>
);
}
// ─── Main Component ───────────────────────────────────────────────────────────
export function PactDebug() {
// Get state from modular stores
const signedPacts = usePrestigeStore((s) => s.signedPacts);
const signedPactDetails = usePrestigeStore((s) => s.signedPactDetails);
const elements = useManaStore((s) => s.elements);
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
// Get actions
const addSignedPact = usePrestigeStore((s) => s.addSignedPact);
const removePact = usePrestigeStore((s) => s.removePact);
const debugSetSignedPacts = usePrestigeStore((s) => s.debugSetSignedPacts);
const debugSetPactDetails = usePrestigeStore((s) => s.debugSetPactDetails);
const unlockElement = useManaStore((s) => s.unlockElement);
// Get log function from uiStore
const addLog = useUIStore((s) => s.addLog);
// Get all guardian floors
const guardianFloors = Object.keys(GUARDIANS || {}).map(Number).sort((a, b) => a - b);
// Force sign a pact with a guardian (bypass costs and time)
const addLog = useUIStore((s) => s.addLog);
const forcePact = (floor: number) => {
const guardian = GUARDIANS[floor];
if (!guardian) return;
// Check if already signed
if (signedPacts.includes(floor)) {
addLog(`⚠️ Already signed pact with ${guardian.name}!`);
return;
}
// Check max pacts
const maxPacts = 1 + (prestigeUpgrades?.pactCapacity || 0);
if (signedPacts.length >= maxPacts) {
addLog(`⚠️ Cannot sign more pacts! Maximum: ${maxPacts}.`);
return;
}
// Force sign the pact
addSignedPact(floor);
// Add pact details
const newSignedPactDetails = {
...signedPactDetails,
[floor]: {
@@ -62,13 +120,11 @@ export function PactDebug() {
addLog(`📜 DEBUG: Pact with ${guardian.name} force-signed!`);
};
// Remove a pact
const removePactHandler = (floor: number) => {
const guardian = GUARDIANS[floor];
removePact(floor);
// Remove pact details
const newSignedPactDetails = { ...signedPactDetails };
delete newSignedPactDetails[floor];
debugSetPactDetails(newSignedPactDetails);
@@ -76,7 +132,6 @@ export function PactDebug() {
addLog(`📜 DEBUG: Removed pact with ${guardian?.name || 'Unknown'}!`);
};
// Clear all pacts
const clearAllPacts = () => {
addLog(`📜 DEBUG: Cleared all pacts!`);
debugSetSignedPacts([]);
@@ -96,74 +151,23 @@ export function PactDebug() {
<p className="text-xs text-gray-400 mb-2">
Force sign pacts with guardians (bypasses mana costs and signing time)
</p>
<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>
);
})}
</div>
{/* Clear All Button */}
<GuardianPactList
signedPacts={signedPacts}
onForceSign={forcePact}
onRemove={removePactHandler}
/>
{signedPacts.length > 0 && (
<div className="pt-2 border-t border-gray-700">
<Button
size="sm"
variant="destructive"
onClick={clearAllPacts}
className="w-full text-xs"
>
<Button size="sm" variant="destructive" onClick={clearAllPacts} className="w-full text-xs">
Clear All Pacts ({signedPacts.length})
</Button>
</div>
)}
{/* Status */}
<div className="text-xs text-gray-400 pt-2 border-t border-gray-700">
Signed Pacts: {signedPacts.length} |
Signed Pacts: {signedPacts.length} |
Max Pacts: {1 + (prestigeUpgrades?.pactCapacity || 0)}
</div>
</div>
@@ -172,4 +176,4 @@ export function PactDebug() {
);
}
PactDebug.displayName = "PactDebug";
PactDebug.displayName = 'PactDebug';