From dbc1b5e02c1ed32f91baa185a0defddc9b72a5d7 Mon Sep 17 00:00:00 2001 From: n8n-gitea Date: Tue, 19 May 2026 22:04:27 +0200 Subject: [PATCH] feat: recreate Equipment Tab with equip/unequip gear management --- docs/circular-deps.txt | 4 +- docs/dependency-graph.json | 2 +- docs/project-structure.txt | 6 + src/app/page.tsx | 10 + src/components/game/tabs/EquipmentTab.test.ts | 215 ++++++++++++++++++ src/components/game/tabs/EquipmentTab.tsx | 95 ++++++++ .../EquipmentTab/EquipmentEffectsSummary.tsx | 93 ++++++++ .../tabs/EquipmentTab/EquipmentSlotGrid.tsx | 79 +++++++ .../game/tabs/EquipmentTab/InventoryList.tsx | 135 +++++++++++ src/components/game/tabs/index.ts | 1 + 10 files changed, 637 insertions(+), 3 deletions(-) create mode 100644 src/components/game/tabs/EquipmentTab.test.ts create mode 100644 src/components/game/tabs/EquipmentTab.tsx create mode 100644 src/components/game/tabs/EquipmentTab/EquipmentEffectsSummary.tsx create mode 100644 src/components/game/tabs/EquipmentTab/EquipmentSlotGrid.tsx create mode 100644 src/components/game/tabs/EquipmentTab/InventoryList.tsx diff --git a/docs/circular-deps.txt b/docs/circular-deps.txt index 2fd2b6a..1242411 100644 --- a/docs/circular-deps.txt +++ b/docs/circular-deps.txt @@ -1,8 +1,8 @@ # Circular Dependencies -Generated: 2026-05-19T16:29:33.501Z +Generated: 2026-05-19T18:19:35.896Z Found: 3 circular chain(s) — these MUST be fixed before modifying involved files. -1. Processed 121 files (1.3s) (4 warnings) +1. Processed 121 files (1.2s) (4 warnings) 2. 1) data/equipment/index.ts > data/equipment/utils.ts 3. 2) data/golems/index.ts > data/golems/utils.ts diff --git a/docs/dependency-graph.json b/docs/dependency-graph.json index 5422abd..dd23d7e 100644 --- a/docs/dependency-graph.json +++ b/docs/dependency-graph.json @@ -1,6 +1,6 @@ { "_meta": { - "generated": "2026-05-19T16:29:32.045Z", + "generated": "2026-05-19T18:19:34.562Z", "description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.", "usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry." }, diff --git a/docs/project-structure.txt b/docs/project-structure.txt index b2b8f02..11b38db 100644 --- a/docs/project-structure.txt +++ b/docs/project-structure.txt @@ -97,6 +97,10 @@ Mana-Loop/ │ │ │ │ │ ├── GolemDebugSection.tsx │ │ │ │ │ ├── PactDebugSection.tsx │ │ │ │ │ └── SpireDebugSection.tsx +│ │ │ │ ├── EquipmentTab/ +│ │ │ │ │ ├── EquipmentEffectsSummary.tsx +│ │ │ │ │ ├── EquipmentSlotGrid.tsx +│ │ │ │ │ └── InventoryList.tsx │ │ │ │ ├── StatsTab/ │ │ │ │ │ ├── CombatStatsSection.tsx │ │ │ │ │ ├── ElementStatsSection.tsx @@ -111,6 +115,8 @@ Mana-Loop/ │ │ │ │ ├── DebugTab.test.ts │ │ │ │ ├── DebugTab.tsx │ │ │ │ ├── DisciplinesTab.tsx +│ │ │ │ ├── EquipmentTab.test.ts +│ │ │ │ ├── EquipmentTab.tsx │ │ │ │ ├── PrestigeTab.test.ts │ │ │ │ ├── PrestigeTab.tsx │ │ │ │ ├── SpellsTab.tsx diff --git a/src/app/page.tsx b/src/app/page.tsx index 4620805..28f132a 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -48,6 +48,7 @@ const DebugTab = lazy(() => import('@/components/game/tabs').then(module => ({ d const AchievementsTab = lazy(() => import('@/components/game/tabs').then(module => ({ default: module.AchievementsTab }))); const AttunementsTab = lazy(() => import('@/components/game/tabs').then(module => ({ default: module.AttunementsTab }))); const PrestigeTab = lazy(() => import('@/components/game/tabs').then(module => ({ default: module.PrestigeTab }))); +const EquipmentTab = lazy(() => import('@/components/game/tabs').then(module => ({ default: module.EquipmentTab }))); const TabLoadingFallback = () =>
Loading...
; @@ -241,6 +242,7 @@ export default function ManaLoopGame() { ⚗️ Attunements 🏆 Achievements ✨ Prestige + ⚔️ Equipment @@ -302,6 +304,14 @@ export default function ManaLoopGame() { + + + equipment tab failed to load.}> + }> + + + + diff --git a/src/components/game/tabs/EquipmentTab.test.ts b/src/components/game/tabs/EquipmentTab.test.ts new file mode 100644 index 0000000..c35dca9 --- /dev/null +++ b/src/components/game/tabs/EquipmentTab.test.ts @@ -0,0 +1,215 @@ +import { describe, it, expect, vi } from 'vitest'; + +// ─── Test: EquipmentTab barrel export ────────────────────────────────────────── + +describe('EquipmentTab module structure', () => { + it('exports EquipmentTab from barrel index', async () => { + const mod = await import('./EquipmentTab'); + expect(mod.EquipmentTab).toBeDefined(); + expect(typeof mod.EquipmentTab).toBe('function'); + }); + + it('EquipmentTab has correct displayName', async () => { + const { EquipmentTab } = await import('./EquipmentTab'); + expect(EquipmentTab.displayName).toBe('EquipmentTab'); + }); +}); + +// ─── Test: Barrel export includes EquipmentTab ───────────────────────────────── + +describe('Tab barrel export', () => { + it('includes EquipmentTab in the tabs index', async () => { + const mod = await import('@/components/game/tabs'); + expect(mod.EquipmentTab).toBeDefined(); + expect(typeof mod.EquipmentTab).toBe('function'); + }); +}); + +// ─── Test: Equipment slot definitions ────────────────────────────────────────── + +describe('Equipment slot definitions', () => { + it('has exactly 8 equipment slots', async () => { + const { EQUIPMENT_SLOTS } = await import('@/lib/game/data/equipment'); + expect(EQUIPMENT_SLOTS).toHaveLength(8); + }); + + it('all slots have display names', async () => { + const { SLOT_NAMES, EQUIPMENT_SLOTS } = await import('@/lib/game/data/equipment'); + for (const slot of EQUIPMENT_SLOTS) { + expect(SLOT_NAMES[slot]).toBeDefined(); + expect(SLOT_NAMES[slot].length).toBeGreaterThan(0); + } + }); + + it('includes mainHand, offHand, head, body, hands, feet, accessory1, accessory2', async () => { + const { EQUIPMENT_SLOTS } = await import('@/lib/game/data/equipment'); + expect(EQUIPMENT_SLOTS).toContain('mainHand'); + expect(EQUIPMENT_SLOTS).toContain('offHand'); + expect(EQUIPMENT_SLOTS).toContain('head'); + expect(EQUIPMENT_SLOTS).toContain('body'); + expect(EQUIPMENT_SLOTS).toContain('hands'); + expect(EQUIPMENT_SLOTS).toContain('feet'); + expect(EQUIPMENT_SLOTS).toContain('accessory1'); + expect(EQUIPMENT_SLOTS).toContain('accessory2'); + }); +}); + +// ─── Test: Equipment type definitions ────────────────────────────────────────── + +describe('Equipment type definitions', () => { + it('all equipment types have required fields', async () => { + const { EQUIPMENT_TYPES } = await import('@/lib/game/data/equipment'); + for (const [id, type] of Object.entries(EQUIPMENT_TYPES)) { + expect(type.id).toBe(id); + expect(type.name).toBeTruthy(); + expect(type.category).toBeTruthy(); + expect(type.slot).toBeTruthy(); + expect(type.baseCapacity).toBeGreaterThan(0); + } + }); + + it('accessory category types can equip in accessory slots', async () => { + const { EQUIPMENT_TYPES, getValidSlotsForEquipmentType } = await import('@/lib/game/data/equipment'); + const accessories = Object.values(EQUIPMENT_TYPES).filter((t) => t.category === 'accessory'); + expect(accessories.length).toBeGreaterThan(0); + for (const acc of accessories) { + const slots = getValidSlotsForEquipmentType(acc); + expect(slots).toContain('accessory1'); + expect(slots).toContain('accessory2'); + } + }); +}); + +// ─── Test: Starting equipment ────────────────────────────────────────────────── + +describe('Starting equipment', () => { + it('createStartingEquipment returns valid equippedInstances', async () => { + const { createStartingEquipment } = await import('@/lib/game/crafting-slice'); + const { equippedInstances, equipmentInstances } = createStartingEquipment(); + + expect(equippedInstances.mainHand).toBeTruthy(); + expect(equippedInstances.body).toBeTruthy(); + expect(equippedInstances.feet).toBeTruthy(); + expect(equippedInstances.offHand).toBeNull(); + expect(equippedInstances.head).toBeNull(); + expect(equippedInstances.hands).toBeNull(); + expect(equippedInstances.accessory1).toBeNull(); + expect(equippedInstances.accessory2).toBeNull(); + + expect(Object.keys(equipmentInstances).length).toBe(3); + }); + + it('starting equipment instances have valid fields', async () => { + const { createStartingEquipment } = await import('@/lib/game/crafting-slice'); + const { equipmentInstances } = createStartingEquipment(); + + for (const instance of Object.values(equipmentInstances)) { + expect(instance.instanceId).toBeTruthy(); + expect(instance.typeId).toBeTruthy(); + expect(instance.name).toBeTruthy(); + expect(instance.rarity).toBe('common'); + expect(instance.quality).toBe(100); + expect(instance.usedCapacity).toBeGreaterThanOrEqual(0); + expect(instance.totalCapacity).toBeGreaterThan(0); + } + }); +}); + +// ─── Test: Equipment actions ─────────────────────────────────────────────────── + +describe('Equipment actions', () => { + it('equipItem is a function', async () => { + const { equipItem } = await import('@/lib/game/crafting-actions/equipment-actions'); + expect(typeof equipItem).toBe('function'); + }); + + it('unequipItem is a function', async () => { + const { unequipItem } = await import('@/lib/game/crafting-actions/equipment-actions'); + expect(typeof unequipItem).toBe('function'); + }); + + it('deleteEquipmentInstance is a function', async () => { + const { deleteEquipmentInstance } = await import('@/lib/game/crafting-actions/equipment-actions'); + expect(typeof deleteEquipmentInstance).toBe('function'); + }); + + it('createEquipmentInstance is a function', async () => { + const { createEquipmentInstance } = await import('@/lib/game/crafting-actions/equipment-actions'); + expect(typeof createEquipmentInstance).toBe('function'); + }); +}); + +// ─── Test: Equipment effects ─────────────────────────────────────────────────── + +describe('Equipment effects computation', () => { + it('computeEquipmentEffects returns empty for no equipment', async () => { + const { computeEquipmentEffects } = await import('@/lib/game/effects'); + const result = computeEquipmentEffects({}, { mainHand: null, offHand: null, head: null, body: null, hands: null, feet: null, accessory1: null, accessory2: null }); + expect(Object.keys(result.bonuses)).toHaveLength(0); + expect(Object.keys(result.multipliers)).toHaveLength(0); + expect(result.specials.size).toBe(0); + }); + + it('computeEquipmentEffects detects enchantment bonuses', async () => { + const { computeEquipmentEffects } = await import('@/lib/game/effects'); + const instances = { + test1: { + instanceId: 'test1', + typeId: 'basicStaff', + name: 'Test Staff', + enchantments: [{ effectId: 'spell_manaBolt', stacks: 1, actualCost: 50 }], + usedCapacity: 50, + totalCapacity: 50, + rarity: 'common' as const, + quality: 100, + tags: [], + }, + }; + const equipped = { mainHand: 'test1', offHand: null, head: null, body: null, hands: null, feet: null, accessory1: null, accessory2: null }; + const result = computeEquipmentEffects(instances, equipped); + // Should at least not crash and return a valid structure + expect(result).toHaveProperty('bonuses'); + expect(result).toHaveProperty('multipliers'); + expect(result).toHaveProperty('specials'); + }); +}); + +// ─── Test: File size limits ──────────────────────────────────────────────────── + +describe('File size limits (400 lines max)', () => { + it('EquipmentTab.tsx is under 400 lines', async () => { + const fs = await import('fs'); + const path = await import('path'); + const filePath = path.join(__dirname, 'EquipmentTab.tsx'); + const content = fs.readFileSync(filePath, 'utf-8'); + const lines = content.split('\n').length; + expect(lines).toBeLessThan(400); + }); + + it('EquipmentSlotGrid.tsx is under 400 lines', async () => { + const fs = await import('fs'); + const path = await import('path'); + const filePath = path.join(__dirname, 'EquipmentTab/EquipmentSlotGrid.tsx'); + const content = fs.readFileSync(filePath, 'utf-8'); + const lines = content.split('\n').length; + expect(lines).toBeLessThan(400); + }); + + it('InventoryList.tsx is under 400 lines', async () => { + const fs = await import('fs'); + const path = await import('path'); + const filePath = path.join(__dirname, 'EquipmentTab/InventoryList.tsx'); + const content = fs.readFileSync(filePath, 'utf-8'); + const lines = content.split('\n').length; + expect(lines).toBeLessThan(400); + }); + + it('EquipmentEffectsSummary.tsx is under 400 lines', async () => { + const fs = await import('fs'); + const path = await import('path'); + const filePath = path.join(__dirname, 'EquipmentTab/EquipmentEffectsSummary.tsx'); + const content = fs.readFileSync(filePath, 'utf-8'); + const lines = content.split('\n').length; + expect(lines).toBeLessThan(400); + }); +}); diff --git a/src/components/game/tabs/EquipmentTab.tsx b/src/components/game/tabs/EquipmentTab.tsx new file mode 100644 index 0000000..06fabd4 --- /dev/null +++ b/src/components/game/tabs/EquipmentTab.tsx @@ -0,0 +1,95 @@ +'use client'; + +import { useState, useEffect, useCallback, useMemo } from 'react'; +import { useCraftingStore } from '@/lib/game/stores/craftingStore'; +import { equipItem, unequipItem, deleteEquipmentInstance } from '@/lib/game/crafting-actions/equipment-actions'; +import type { EquipmentSlot } from '@/lib/game/types'; +import { DebugName } from '@/components/game/debug/debug-context'; +import { EquipmentSlotGrid } from './EquipmentTab/EquipmentSlotGrid'; +import { InventoryList } from './EquipmentTab/InventoryList'; +import { EquipmentEffectsSummary } from './EquipmentTab/EquipmentEffectsSummary'; + +export function EquipmentTab() { + const [mounted, setMounted] = useState(false); + + const equippedInstances = useCraftingStore((s) => s.equippedInstances); + const equipmentInstances = useCraftingStore((s) => s.equipmentInstances); + const storeEquipItem = useCraftingStore((s) => s.equipItem); + const storeUnequipItem = useCraftingStore((s) => s.unequipItem); + const storeDeleteEquipment = useCraftingStore((s) => s.deleteEquipmentInstance); + + useEffect(() => { + setMounted(true); + }, []); + + const handleEquip = useCallback( + (instanceId: string, slot: EquipmentSlot) => { + storeEquipItem(instanceId, slot); + }, + [storeEquipItem] + ); + + const handleUnequip = useCallback( + (slot: EquipmentSlot) => { + storeUnequipItem(slot); + }, + [storeUnequipItem] + ); + + const handleDelete = useCallback( + (instanceId: string) => { + storeDeleteEquipment(instanceId); + }, + [storeDeleteEquipment] + ); + + const inventoryItems = useMemo( + () => + Object.entries(equipmentInstances).filter( + ([id]) => !Object.values(equippedInstances).includes(id) + ), + [equipmentInstances, equippedInstances] + ); + + if (!mounted) { + return ( +
+ Loading equipment… +
+ ); + } + + return ( + +
+
+

Equipped Gear

+ +
+ + + +
+

+ Inventory ({inventoryItems.length}) +

+ +
+
+
+ ); +} + +EquipmentTab.displayName = 'EquipmentTab'; diff --git a/src/components/game/tabs/EquipmentTab/EquipmentEffectsSummary.tsx b/src/components/game/tabs/EquipmentTab/EquipmentEffectsSummary.tsx new file mode 100644 index 0000000..50af690 --- /dev/null +++ b/src/components/game/tabs/EquipmentTab/EquipmentEffectsSummary.tsx @@ -0,0 +1,93 @@ +'use client'; + +import { computeEquipmentEffects } from '@/lib/game/effects'; +import type { EquipmentInstance } from '@/lib/game/types'; + +interface EquipmentEffectsSummaryProps { + equipmentInstances: Record; + equippedInstances: Record; +} + +const BONUS_LABELS: Record = { + maxMana: 'Max Mana', + regen: 'Mana Regen', + clickMana: 'Click Mana', + baseDamage: 'Base Damage', + elementCap: 'Element Cap', + critChance: 'Crit Chance', + attackSpeed: 'Attack Speed', + meditationEfficiency: 'Meditation Efficiency', + studySpeed: 'Study Speed', +}; + +const MULT_LABELS: Record = { + maxMana: 'Max Mana', + regen: 'Mana Regen', + clickMana: 'Click Mana', + baseDamage: 'Base Damage', + attackSpeed: 'Attack Speed', + elementCap: 'Element Cap', + meditationEfficiency: 'Meditation Efficiency', + studySpeed: 'Study Speed', +}; + +export function EquipmentEffectsSummary({ equipmentInstances, equippedInstances }: EquipmentEffectsSummaryProps) { + const { bonuses, multipliers, specials } = computeEquipmentEffects(equipmentInstances, equippedInstances); + + const bonusEntries = Object.entries(bonuses).filter(([, v]) => v !== 0); + const multEntries = Object.entries(multipliers).filter(([, v]) => v !== 1); + const specialEntries = Array.from(specials); + + if (bonusEntries.length === 0 && multEntries.length === 0 && specialEntries.length === 0) { + return null; + } + + return ( +
+

Equipment Effects

+ + {bonusEntries.length > 0 && ( +
+
Bonuses
+ {bonusEntries.map(([key, value]) => ( +
+ + {BONUS_LABELS[key] || key} + + + {value > 0 ? '+' : ''}{typeof value === 'number' && !Number.isInteger(value) ? value.toFixed(2) : value} + +
+ ))} +
+ )} + + {multEntries.length > 0 && ( +
+
Multipliers
+ {multEntries.map(([key, value]) => ( +
+ + {MULT_LABELS[key] || key} + + + ×{typeof value === 'number' ? value.toFixed(2) : value} + +
+ ))} +
+ )} + + {specialEntries.length > 0 && ( +
+
Specials
+ {specialEntries.map((id) => ( +
+ ★ {id} +
+ ))} +
+ )} +
+ ); +} diff --git a/src/components/game/tabs/EquipmentTab/EquipmentSlotGrid.tsx b/src/components/game/tabs/EquipmentTab/EquipmentSlotGrid.tsx new file mode 100644 index 0000000..23da791 --- /dev/null +++ b/src/components/game/tabs/EquipmentTab/EquipmentSlotGrid.tsx @@ -0,0 +1,79 @@ +'use client'; + +import { Package } from 'lucide-react'; +import type { EquipmentInstance, EquipmentSlot } from '@/lib/game/types'; +import { EQUIPMENT_TYPES, SLOT_NAMES } from '@/lib/game/data/equipment'; +import { RARITY_CSS_VAR } from '@/components/game/LootInventory/types'; +import { CATEGORY_ICONS } from '@/components/game/LootInventory/icons'; +import { ActionButton } from '@/components/ui/action-button'; + +interface EquipmentSlotGridProps { + equippedInstances: Record; + equipmentInstances: Record; + onUnequip: (slot: EquipmentSlot) => void; +} + +const SLOTS: EquipmentSlot[] = ['mainHand', 'offHand', 'head', 'body', 'hands', 'feet', 'accessory1', 'accessory2']; + +export function EquipmentSlotGrid({ equippedInstances, equipmentInstances, onUnequip }: EquipmentSlotGridProps) { + return ( +
+ {SLOTS.map((slot) => { + const instanceId = equippedInstances[slot]; + const instance = instanceId ? equipmentInstances[instanceId] : null; + + if (instance) { + const type = EQUIPMENT_TYPES[instance.typeId]; + const Icon = type ? CATEGORY_ICONS[type.category] || Package : Package; + const rarityColor = RARITY_CSS_VAR[instance.rarity] || 'var(--rarity-common)'; + + return ( +
+
+ {SLOT_NAMES[slot]} + +
+
+ {instance.name} +
+
+ {type?.name || instance.typeId} +
+
+ {instance.usedCapacity}/{instance.totalCapacity} cap +
+
+ {instance.enchantments.length} enchant{instance.enchantments.length !== 1 ? 's' : ''} • {instance.quality}% quality +
+ onUnequip(slot)} + > + Unequip + +
+ ); + } + + return ( +
+
+ {SLOT_NAMES[slot]} + +
+
Empty
+
+ ); + })} +
+ ); +} diff --git a/src/components/game/tabs/EquipmentTab/InventoryList.tsx b/src/components/game/tabs/EquipmentTab/InventoryList.tsx new file mode 100644 index 0000000..466de65 --- /dev/null +++ b/src/components/game/tabs/EquipmentTab/InventoryList.tsx @@ -0,0 +1,135 @@ +'use client'; + +import { useState } from 'react'; +import { Package, Trash2 } from 'lucide-react'; +import type { EquipmentInstance, EquipmentSlot } from '@/lib/game/types'; +import { EQUIPMENT_TYPES, getValidSlotsForEquipmentType } from '@/lib/game/data/equipment'; +import { RARITY_CSS_VAR, RARITY_GLOW_CSS_VAR } from '@/components/game/LootInventory/types'; +import { CATEGORY_ICONS } from '@/components/game/LootInventory/icons'; +import { ActionButton } from '@/components/ui/action-button'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from '@/components/ui/alert-dialog'; + +interface InventoryListProps { + inventoryItems: [string, EquipmentInstance][]; + equippedInstances: Record; + onEquip: (instanceId: string, slot: EquipmentSlot) => boolean; + onDelete: (instanceId: string) => void; +} + +export function InventoryList({ inventoryItems, equippedInstances, onEquip, onDelete }: InventoryListProps) { + const [selectedSlot, setSelectedSlot] = useState>({}); + + if (inventoryItems.length === 0) { + return ( +
+ No items in inventory. Craft or find equipment to fill your slots. +
+ ); + } + + return ( +
+ {inventoryItems.map(([instanceId, instance]) => { + const type = EQUIPMENT_TYPES[instance.typeId]; + const Icon = type ? CATEGORY_ICONS[type.category] || Package : Package; + const rarityColor = RARITY_CSS_VAR[instance.rarity] || 'var(--rarity-common)'; + const rarityGlow = RARITY_GLOW_CSS_VAR[instance.rarity] || 'var(--rarity-common-glow)'; + const validSlots = type ? getValidSlotsForEquipmentType(type) : []; + const chosenSlot = selectedSlot[instanceId]; + const availableSlots = validSlots.filter((s) => !equippedInstances[s]); + + return ( +
+ +
+
+ {instance.name} +
+
+ {type?.name || instance.typeId} • {instance.usedCapacity}/{instance.totalCapacity} cap +
+
+ {instance.rarity} • {instance.enchantments.length} enchant{instance.enchantments.length !== 1 ? 's' : ''} • {instance.quality}% quality +
+
+
+ {availableSlots.length > 1 ? ( + + ) : null} + { + const slot = availableSlots.length === 1 ? availableSlots[0] : chosenSlot; + if (slot) { + onEquip(instanceId, slot); + setSelectedSlot((prev) => { + const next = { ...prev }; + delete next[instanceId]; + return next; + }); + } + }} + disabled={availableSlots.length === 0 || (availableSlots.length > 1 && !chosenSlot)} + > + Equip + + + + + + + + + + Delete {instance.name}? + + This action cannot be undone. The item will be permanently destroyed. + + + + Cancel + onDelete(instanceId)}> + Delete + + + + +
+
+ ); + })} +
+ ); +} diff --git a/src/components/game/tabs/index.ts b/src/components/game/tabs/index.ts index df5ce8b..65209ce 100644 --- a/src/components/game/tabs/index.ts +++ b/src/components/game/tabs/index.ts @@ -8,3 +8,4 @@ export { DebugTab } from './DebugTab'; export { AchievementsTab } from './AchievementsTab'; export { AttunementsTab } from './AttunementsTab'; export { PrestigeTab } from './PrestigeTab'; +export { EquipmentTab } from './EquipmentTab';