fix: split SpireTab.tsx to 395 lines, remove require() imports, import from data modules; complete store migration
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 30m15s

This commit is contained in:
Refactoring Agent
2026-05-04 13:36:10 +02:00
parent 0eabd604b0
commit 837d963b63
41 changed files with 727 additions and 3935 deletions
+59 -62
View File
@@ -3,70 +3,67 @@
import { GameCard, StatRow, ElementBadge, ActionButton } from '@/components/ui';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Separator } from '@/components/ui/separator';
import {
Mountain, Zap, Clock, Swords, Target, Sparkles, Lock, Check, X,
import {
Mountain, Zap, Clock, Swords, Sparkles, Lock, Check, X,
Info, HelpCircle
} from 'lucide-react';
import { GOLEMS_DEF, getGolemSlots, isGolemUnlocked, getGolemDamage, getGolemAttackSpeed, getGolemFloorDuration } from '@/lib/game/data/golems';
import { ELEMENTS } from '@/lib/game/constants';
import type { GameStore } from '@/lib/game/store';
import { useGameStore, useManaStore, useSkillStore, useCombatStore } from '@/lib/game/stores';
export interface GolemancyTabProps {
store: GameStore;
}
export function GolemancyTab() {
const attunements = useGameStore((s) => s.attunements);
const elements = useManaStore((s) => s.elements);
const skills = useSkillStore((s) => s.skills);
const golemancy = useGameStore((s) => s.golemancy);
const currentFloor = useCombatStore((s) => s.currentFloor);
const currentRoom = useGameStore((s) => s.currentRoom);
const toggleGolem = useGameStore((s) => s.toggleGolem);
const rawMana = useManaStore((s) => s.rawMana);
export function GolemancyTab({ store }: GolemancyTabProps) {
const attunements = store.attunements;
const elements = store.elements;
const skills = store.skills;
const golemancy = store.golemancy;
const currentFloor = store.currentFloor;
const currentRoom = store.currentRoom;
const toggleGolem = store.toggleGolem;
// Get Fabricator level and golem slots
const fabricatorLevel = attunements.fabricator?.level || 0;
const fabricatorActive = attunements.fabricator?.active || false;
const maxSlots = getGolemSlots(fabricatorLevel);
// Get unlocked elements
const unlockedElements = Object.entries(elements)
.filter(([, e]) => e.unlocked)
.map(([id]) => id);
// Get all unlocked golems
const unlockedGolems = Object.values(GOLEMS_DEF || {}).filter(golem =>
const unlockedGolems = Object.values(GOLEMS_DEF || {}).filter(golem =>
isGolemUnlocked(golem.id, attunements, unlockedElements)
);
// Check if golemancy is available
const hasGolemancy = fabricatorActive && fabricatorLevel >= 2;
// Check if currently in combat (not puzzle)
const inCombat = currentRoom.roomType !== 'puzzle';
const inCombat = currentRoom?.roomType !== 'puzzle';
// Get element info helper
const getElementInfo = (elementId: string) => {
return ELEMENTS[elementId];
};
// Render a golem card
const renderGolemCard = (golemId: string, isUnlocked: boolean) => {
const golem = GOLEMS_DEF[golemId];
if (!golem) return null;
const isEnabled = golemancy.enabledGolems.includes(golemId);
const isSelected = golemancy.summonedGolems.some(g => g.golemId === golemId);
// Calculate effective stats
const damage = getGolemDamage(golemId, skills);
const attackSpeed = getGolemAttackSpeed(golemId, skills);
const floorDuration = getGolemFloorDuration(skills);
// Get element color
const primaryElement = getElementInfo(golem.baseManaType);
const elementId = golem.baseManaType;
if (!isUnlocked) {
// Locked golem card
return (
@@ -91,14 +88,14 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
</GameCard>
);
}
return (
<GameCard
key={golemId}
<GameCard
key={golemId}
variant={isEnabled ? "default" : "sunken"}
className={`transition-all cursor-pointer border-2 ${
isEnabled
? 'border-[var(--color-success)] bg-[var(--bg-surface)]'
isEnabled
? 'border-[var(--color-success)] bg-[var(--bg-surface)]'
: 'border-[var(--border-subtle)] hover:border-[var(--border-default)]'
}`}
onClick={() => toggleGolem(golemId)}
@@ -119,7 +116,7 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
</span>
)}
<span className="text-xs px-1.5 py-0.5 border border-[var(--border-default)] rounded">
T{golem.tier}
{golem.tier}
</span>
{isEnabled ? (
<Check className="w-4 h-4 text-[var(--color-success)]" />
@@ -131,35 +128,35 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
</div>
<div className="space-y-2">
<p className="text-xs text-[var(--text-secondary)]">{golem.description}</p>
<Separator className="bg-[var(--border-subtle)]" />
<div className="grid grid-cols-2 gap-2 text-xs">
<StatRow label="DMG:" value={damage.toString()} />
<StatRow label="Speed:" value={`${attackSpeed.toFixed(1)}/hr`} />
<StatRow label="Pierce:" value={`${Math.floor(golem.armorPierce * 100)}%`} />
<StatRow label="Duration:" value={`${floorDuration} floor(s)`} />
</div>
<Separator className="bg-[var(--border-subtle)]" />
{/* Summon Cost */}
<div>
<div className="text-xs text-[var(--text-secondary)] mb-1">Summon Cost:</div>
<div className="flex flex-wrap gap-1">
{golem.summonCost.map((cost, idx) => {
const elem = getElementInfo(cost.element || '');
const available = cost.type === 'raw'
? store.rawMana
const available = cost.type === 'raw'
? rawMana
: elements[cost.element || '']?.current || 0;
const canAfford = available >= cost.amount;
return (
<span
<span
key={idx}
className={`text-xs px-1.5 py-0.5 border rounded ${
canAfford
? 'border-[var(--color-success)] text-[var(--color-success)]'
canAfford
? 'border-[var(--color-success)] text-[var(--color-success)]'
: 'border-[var(--color-danger)] text-[var(--color-danger)]'
}`}
>
@@ -170,7 +167,7 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
})}
</div>
</div>
{/* Maintenance Cost */}
<div>
<div className="text-xs text-[var(--text-secondary)] mb-1">Maintenance/hr:</div>
@@ -185,7 +182,7 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
})}
</div>
</div>
{/* Status */}
{isSelected && (
<div className="mt-2 text-xs text-[var(--color-success)] flex items-center gap-1">
@@ -197,7 +194,7 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
</GameCard>
);
};
return (
<div className="space-y-4">
{/* Header */}
@@ -216,26 +213,26 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
</div>
) : (
<>
<StatRow
label="Golem Slots:"
<StatRow
label="Golem Slots:"
value={`${golemancy.enabledGolems.length} / ${maxSlots}`}
highlight={golemancy.enabledGolems.length > 0 ? 'success' : undefined}
/>
<StatRow
label="Fabricator Level:"
<StatRow
label="Fabricator Level:"
value={fabricatorLevel.toString()}
highlight="warning"
/>
<StatRow
label="Floor Duration:"
<StatRow
label="Floor Duration:"
value={`${getGolemFloorDuration(skills)} floor(s)`}
/>
<StatRow
label="Status:"
<StatRow
label="Status:"
value={inCombat ? 'Combat Active' : 'Puzzle Room (No Golems)'}
highlight={inCombat ? 'success' : 'warning'}
/>
<p className="text-xs text-[var(--text-muted)] mt-2">
Golems are automatically summoned at the start of each combat floor.
They cost mana to maintain and will be dismissed if you run out.
@@ -244,7 +241,7 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
)}
</div>
</GameCard>
{/* Active Golems - Empty State */}
{hasGolemancy && golemancy.summonedGolems.length === 0 && (
<GameCard variant="sunken">
@@ -255,12 +252,12 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
</div>
</GameCard>
)}
{/* Active Golems */}
{hasGolemancy && golemancy.summonedGolems.length > 0 && (
<GameCard variant="default" className="border-[var(--color-success)]">
<div className="pb-2">
<h3 className="text-sm font-semibold text-[var(--color-success)] flex items-center gap-2">
<h3 className="text-sm font-semibold flex items-center gap-2 text-[var(--color-success)]">
<Sparkles className="w-4 h-4" />
Active Golems ({golemancy.summonedGolems.length})
</h3>
@@ -269,7 +266,7 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
{golemancy.summonedGolems.map(sg => {
const golem = GOLEMS_DEF[sg.golemId];
if (!golem) return null;
return (
<span key={sg.golemId} className="text-xs px-2 py-1 border border-[var(--border-default)] rounded">
<Mountain className="w-3 h-3 inline mr-1" style={{ color: `var(--mana-${golem.baseManaType})` }} />
@@ -280,7 +277,7 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
</div>
</GameCard>
)}
{/* Golem Selection */}
{hasGolemancy && (
<GameCard>
@@ -291,7 +288,7 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 pr-4">
{/* Unlocked Golems */}
{unlockedGolems.map(golem => renderGolemCard(golem.id, true))}
{/* Locked Golems */}
{Object.values(GOLEMS_DEF || {})
.filter(g => !isGolemUnlocked(g.id, attunements, unlockedElements))
@@ -300,7 +297,7 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
</ScrollArea>
</GameCard>
)}
{/* Golemancy Skills Info */}
<GameCard>
<div className="pb-2">