refactor: extract sub-components from monster functions (issue #99)
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m21s
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:
@@ -23,6 +23,101 @@ import {
|
||||
AlertDialogTrigger,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
|
||||
// ─── Stat Cell ────────────────────────────────────────────────────────────────
|
||||
|
||||
function PrestigeStatCell({ value, label, color }: { value: string | number; label: string; color: string }) {
|
||||
return (
|
||||
<div className="text-center">
|
||||
<div className={`text-2xl font-bold ${color}`}>{value}</div>
|
||||
<div className="text-xs text-gray-400 mt-0.5">{label}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Insight Summary ──────────────────────────────────────────────────────────
|
||||
|
||||
function InsightSummary({ insight, totalInsight, loopCount, loopInsight }: {
|
||||
insight: number;
|
||||
totalInsight: number;
|
||||
loopCount: number;
|
||||
loopInsight: number;
|
||||
}) {
|
||||
return (
|
||||
<Card className="bg-gray-900/60 border-gray-700">
|
||||
<CardContent className="py-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<PrestigeStatCell value={fmt(insight)} label="Insight Available" color="text-amber-400" />
|
||||
<PrestigeStatCell value={fmt(totalInsight)} label="Total Insight Earned" color="text-gray-200" />
|
||||
<PrestigeStatCell value={loopCount} label="Loops Completed" color="text-gray-200" />
|
||||
<PrestigeStatCell value={fmt(loopInsight)} label="This Loop's Insight" color="text-purple-400" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Memories Card ────────────────────────────────────────────────────────────
|
||||
|
||||
function MemoriesCard({ memories, memorySlots }: { memories: { skillId: string; level: number; tier: number }[]; memorySlots: number }) {
|
||||
return (
|
||||
<Card className="bg-gray-900/60 border-gray-700">
|
||||
<SectionHeader title="🧠 Memories" />
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-xs text-gray-400 mb-2">
|
||||
Skills carried between loops. Slots: {memories.length}/{memorySlots}
|
||||
</p>
|
||||
{memories.length === 0 ? (
|
||||
<p className="text-xs text-gray-500 italic">No memories stored yet.</p>
|
||||
) : (
|
||||
<div className="space-y-1">
|
||||
{memories.map((m) => (
|
||||
<div key={m.skillId} className="text-xs text-gray-300 flex justify-between">
|
||||
<span>{m.skillId}</span>
|
||||
<span className="text-gray-500">Lv.{m.level} T{m.tier}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Pacts Card ───────────────────────────────────────────────────────────────
|
||||
|
||||
function PactsCard({ signedPacts, pactSlots, defeatedGuardians }: {
|
||||
signedPacts: number[];
|
||||
pactSlots: number;
|
||||
defeatedGuardians: number[];
|
||||
}) {
|
||||
return (
|
||||
<Card className="bg-gray-900/60 border-gray-700">
|
||||
<SectionHeader title="📜 Pacts" />
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-xs text-gray-400 mb-2">
|
||||
Guardian pacts signed. Slots: {signedPacts.length}/{pactSlots}
|
||||
</p>
|
||||
{defeatedGuardians.length > 0 && (
|
||||
<p className="text-xs text-gray-500 mb-1">
|
||||
Defeated guardians: {defeatedGuardians.map((f) => `F${f}`).join(', ')}
|
||||
</p>
|
||||
)}
|
||||
{signedPacts.length === 0 ? (
|
||||
<p className="text-xs text-gray-500 italic">No pacts signed yet.</p>
|
||||
) : (
|
||||
<div className="space-y-1">
|
||||
{signedPacts.map((f) => (
|
||||
<div key={f} className="text-xs text-green-400">
|
||||
✓ Floor {f} — Pact signed
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Upgrade Card ─────────────────────────────────────────────────────────────
|
||||
|
||||
interface UpgradeCardProps {
|
||||
@@ -72,6 +167,49 @@ function UpgradeCard({ id, name, desc, max, cost, currentLevel, insight, onPurch
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Reset Loop Section ──────────────────────────────────────────────────────
|
||||
|
||||
function ResetLoopSection({ loopInsight, onReset }: { loopInsight: number; onReset: () => void }) {
|
||||
return (
|
||||
<Card className="bg-gray-900/60 border-red-900/50">
|
||||
<CardContent className="py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-red-400">Reset Loop</h3>
|
||||
<p className="text-xs text-gray-400 mt-1">
|
||||
End the current loop and gain {fmt(loopInsight)} insight. Your prestige upgrades, memories, and pacts are preserved.
|
||||
</p>
|
||||
</div>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="destructive" size="sm" className="flex-shrink-0 ml-4">
|
||||
Reset Loop
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Reset the Loop?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This will end your current loop and award you <strong className="text-amber-400">{fmt(loopInsight)} insight</strong>.
|
||||
Your prestige upgrades, memories, and pacts will be preserved.
|
||||
<br /><br />
|
||||
Day, hour, mana, floor progress, and combat state will be reset.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={onReset}>
|
||||
Confirm Reset
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Main Component ───────────────────────────────────────────────────────────
|
||||
|
||||
export function PrestigeTab() {
|
||||
@@ -130,80 +268,18 @@ export function PrestigeTab() {
|
||||
return (
|
||||
<DebugName name="PrestigeTab">
|
||||
<div className="space-y-4">
|
||||
{/* Insight & Loop Summary */}
|
||||
<Card className="bg-gray-900/60 border-gray-700">
|
||||
<CardContent className="py-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-amber-400">{fmt(insight)}</div>
|
||||
<div className="text-xs text-gray-400 mt-0.5">Insight Available</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-gray-200">{fmt(totalInsight)}</div>
|
||||
<div className="text-xs text-gray-400 mt-0.5">Total Insight Earned</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-gray-200">{loopCount}</div>
|
||||
<div className="text-xs text-gray-400 mt-0.5">Loops Completed</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-purple-400">{fmt(loopInsight)}</div>
|
||||
<div className="text-xs text-gray-400 mt-0.5">This Loop's Insight</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<InsightSummary
|
||||
insight={insight}
|
||||
totalInsight={totalInsight}
|
||||
loopCount={loopCount}
|
||||
loopInsight={loopInsight}
|
||||
/>
|
||||
|
||||
{/* Memories & Pacts */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Card className="bg-gray-900/60 border-gray-700">
|
||||
<SectionHeader title="🧠 Memories" />
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-xs text-gray-400 mb-2">
|
||||
Skills carried between loops. Slots: {memories.length}/{memorySlots}
|
||||
</p>
|
||||
{memories.length === 0 ? (
|
||||
<p className="text-xs text-gray-500 italic">No memories stored yet.</p>
|
||||
) : (
|
||||
<div className="space-y-1">
|
||||
{memories.map((m) => (
|
||||
<div key={m.skillId} className="text-xs text-gray-300 flex justify-between">
|
||||
<span>{m.skillId}</span>
|
||||
<span className="text-gray-500">Lv.{m.level} T{m.tier}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gray-900/60 border-gray-700">
|
||||
<SectionHeader title="📜 Pacts" />
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-xs text-gray-400 mb-2">
|
||||
Guardian pacts signed. Slots: {signedPacts.length}/{pactSlots}
|
||||
</p>
|
||||
{defeatedGuardians.length > 0 && (
|
||||
<p className="text-xs text-gray-500 mb-1">
|
||||
Defeated guardians: {defeatedGuardians.map((f) => `F${f}`).join(', ')}
|
||||
</p>
|
||||
)}
|
||||
{signedPacts.length === 0 ? (
|
||||
<p className="text-xs text-gray-500 italic">No pacts signed yet.</p>
|
||||
) : (
|
||||
<div className="space-y-1">
|
||||
{signedPacts.map((f) => (
|
||||
<div key={f} className="text-xs text-green-400">
|
||||
✓ Floor {f} — Pact signed
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<MemoriesCard memories={memories} memorySlots={memorySlots} />
|
||||
<PactsCard signedPacts={signedPacts} pactSlots={pactSlots} defeatedGuardians={defeatedGuardians} />
|
||||
</div>
|
||||
|
||||
{/* Prestige Upgrades */}
|
||||
<Card className="bg-gray-900/60 border-gray-700">
|
||||
<SectionHeader title="⬆️ Prestige Upgrades" />
|
||||
<CardContent className="pt-0">
|
||||
@@ -227,43 +303,7 @@ export function PrestigeTab() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Reset Loop */}
|
||||
<Card className="bg-gray-900/60 border-red-900/50">
|
||||
<CardContent className="py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-red-400">Reset Loop</h3>
|
||||
<p className="text-xs text-gray-400 mt-1">
|
||||
End the current loop and gain {fmt(loopInsight)} insight. Your prestige upgrades, memories, and pacts are preserved.
|
||||
</p>
|
||||
</div>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="destructive" size="sm" className="flex-shrink-0 ml-4">
|
||||
Reset Loop
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Reset the Loop?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This will end your current loop and award you <strong className="text-amber-400">{fmt(loopInsight)} insight</strong>.
|
||||
Your prestige upgrades, memories, and pacts will be preserved.
|
||||
<br /><br />
|
||||
Day, hour, mana, floor progress, and combat state will be reset.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={handleResetLoop}>
|
||||
Confirm Reset
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<ResetLoopSection loopInsight={loopInsight} onReset={handleResetLoop} />
|
||||
</div>
|
||||
</DebugName>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user