fix: update EquipmentTab to use modular stores, fixing tab crash
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m43s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m43s
This commit is contained in:
@@ -11,18 +11,20 @@ import {
|
|||||||
} from '@/lib/game/data/equipment';
|
} from '@/lib/game/data/equipment';
|
||||||
import { ENCHANTMENT_EFFECTS } from '@/lib/game/data/enchantment-effects';
|
import { ENCHANTMENT_EFFECTS } from '@/lib/game/data/enchantment-effects';
|
||||||
import { getUnifiedEffects } from '@/lib/game/effects';
|
import { getUnifiedEffects } from '@/lib/game/effects';
|
||||||
import { fmt } from '@/lib/game/store';
|
import { fmt } from '@/lib/game/stores';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { GameCard } from '@/components/ui/game-card';
|
import { GameCard } from '@/components/ui/game-card';
|
||||||
import { SectionHeader } from '@/components/ui/section-header';
|
import { SectionHeader } from '@/components/ui/section-header';
|
||||||
import { StatRow } from '@/components/ui/stat-row';
|
import { StatRow } from '@/components/ui/stat-row';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import type { GameStore, EquipmentInstance } from '@/lib/game/types';
|
import type { EquipmentInstance } from '@/lib/game/types';
|
||||||
import { EquipmentSlotGrid } from './EquipmentSlotGrid';
|
import { EquipmentSlotGrid } from './EquipmentSlotGrid';
|
||||||
import { EquipmentInventory } from './EquipmentInventory';
|
import { EquipmentInventory } from './EquipmentInventory';
|
||||||
import { EnchantmentsPanel } from './EnchantmentsPanel';
|
import { EnchantmentsPanel } from './EnchantmentsPanel';
|
||||||
import { useGameToast } from '@/components/game/GameToast';
|
import { useGameToast } from '@/components/game/GameToast';
|
||||||
import { ConfirmDialog } from '@/components/game/ConfirmDialog';
|
import { ConfirmDialog } from '@/components/game/ConfirmDialog';
|
||||||
|
import { equipItem, unequipItem, deleteEquipmentInstance } from '@/lib/game/crafting-actions';
|
||||||
|
import { useCombatStore } from '@/lib/game/stores';
|
||||||
|
|
||||||
// Rarity color mappings using design system tokens
|
// Rarity color mappings using design system tokens
|
||||||
export const RARITY_BORDER_COLORS: Record<string, string> = {
|
export const RARITY_BORDER_COLORS: Record<string, string> = {
|
||||||
@@ -33,7 +35,6 @@ export const RARITY_BORDER_COLORS: Record<string, string> = {
|
|||||||
legendary: 'border-[var(--mana-light)]',
|
legendary: 'border-[var(--mana-light)]',
|
||||||
mythic: 'border-[var(--mana-dark)]',
|
mythic: 'border-[var(--mana-dark)]',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RARITY_BG_COLORS: Record<string, string> = {
|
export const RARITY_BG_COLORS: Record<string, string> = {
|
||||||
common: 'bg-[var(--bg-sunken)]/30',
|
common: 'bg-[var(--bg-sunken)]/30',
|
||||||
uncommon: 'bg-[var(--color-success)]/10',
|
uncommon: 'bg-[var(--color-success)]/10',
|
||||||
@@ -42,7 +43,6 @@ export const RARITY_BG_COLORS: Record<string, string> = {
|
|||||||
legendary: 'bg-[var(--mana-light)]/10',
|
legendary: 'bg-[var(--mana-light)]/10',
|
||||||
mythic: 'bg-[var(--mana-dark)]/10',
|
mythic: 'bg-[var(--mana-dark)]/10',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RARITY_TEXT_COLORS: Record<string, string> = {
|
export const RARITY_TEXT_COLORS: Record<string, string> = {
|
||||||
common: 'text-[var(--text-secondary)]',
|
common: 'text-[var(--text-secondary)]',
|
||||||
uncommon: 'text-[var(--color-success)]',
|
uncommon: 'text-[var(--color-success)]',
|
||||||
@@ -61,7 +61,7 @@ const SLOT_ICONS: Record<EquipmentSlot, React.ElementType> = {
|
|||||||
),
|
),
|
||||||
offHand: () => (
|
offHand: () => (
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||||
<path d="M20 7H9a2 2 0 01-2-2V4a2 2 0 012-2h5l3 6-3 6h-5a2 2 0 00-2 2v4a2 2 0 002 2h7l3 6-3 6H4a2 2 0 01-2-2v-2" />
|
<path d="M20 7H9a2 2 0 01-2-2V4a2 2 0 012-2h5l3 6-3 6h-5a2 2 0 01-2 2v4a2 2 0 002 2h7l3 6-3 6H4a2 2 0 01-2-2v-2" />
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
head: () => (
|
head: () => (
|
||||||
@@ -114,44 +114,57 @@ const SLOT_GROUPS: SlotGroup[] = [
|
|||||||
{ label: 'Accessories', slots: ['accessory1', 'accessory2'] },
|
{ label: 'Accessories', slots: ['accessory1', 'accessory2'] },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function EquipmentTab({ store }: { store: GameStore }) {
|
export function EquipmentTab() {
|
||||||
const showToast = useGameToast();
|
const showToast = useGameToast();
|
||||||
const [selectedSlot, setSelectedSlot] = useState<EquipmentSlot | null>(null);
|
const [selectedSlot, setSelectedSlot] = useState<EquipmentSlot | null>(null);
|
||||||
const [deleteConfirm, setDeleteConfirm] = useState<{ instanceId: string; name: string } | null>(null);
|
const [deleteConfirm, setDeleteConfirm] = useState<{ instanceId: string; name: string } | null>(null);
|
||||||
|
|
||||||
|
// Use modular store directly
|
||||||
|
const equippedInstances = useCombatStore((s) => s.equippedInstances);
|
||||||
|
const equipmentInstances = useCombatStore((s) => s.equipmentInstances);
|
||||||
|
|
||||||
|
// Guard against undefined during initialization
|
||||||
|
if (!equippedInstances || !equipmentInstances) {
|
||||||
|
return (
|
||||||
|
<div className="p-4 text-center text-[var(--text-muted)]">
|
||||||
|
Loading equipment data...
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Get unequipped items
|
// Get unequipped items
|
||||||
const equippedIds = useMemo(() =>
|
const equippedIds = useMemo(() =>
|
||||||
new Set(Object.values(store.equippedInstances).filter(Boolean)),
|
new Set(Object.values(equippedInstances).filter(Boolean)),
|
||||||
[store.equippedInstances]
|
[equippedInstances]
|
||||||
);
|
);
|
||||||
|
|
||||||
const unequippedItems = useMemo(() =>
|
const unequippedItems = useMemo(() =>
|
||||||
Object.values(store.equipmentInstances).filter(
|
Object.values(equipmentInstances).filter(
|
||||||
(inst) => !equippedIds.has(inst.instanceId)
|
(inst) => !equippedIds.has(inst.instanceId)
|
||||||
),
|
),
|
||||||
[store.equipmentInstances, equippedIds]
|
[equipmentInstances, equippedIds]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Equip an item to a slot
|
// Equip an item to a slot
|
||||||
const handleEquip = (instanceId: string, slot: EquipmentSlot) => {
|
const handleEquip = (instanceId: string, slot: EquipmentSlot) => {
|
||||||
const instance = store.equipmentInstances[instanceId];
|
const instance = equipmentInstances[instanceId];
|
||||||
store.equipItem(instanceId, slot);
|
equipItem(instanceId, slot, useCombatStore.getState, (fn) => useCombatStore.setState(fn));
|
||||||
setSelectedSlot(null);
|
setSelectedSlot(null);
|
||||||
showToast('success', 'Item Equipped', `${instance?.name || 'Item'} equipped to ${SLOT_NAMES[slot]}`);
|
showToast('success', 'Item Equipped', `${instance?.name || 'Item'} equipped to ${SLOT_NAMES[slot]}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Unequip from a slot
|
// Unequip from a slot
|
||||||
const handleUnequip = (slot: EquipmentSlot) => {
|
const handleUnequip = (slot: EquipmentSlot) => {
|
||||||
const instanceId = store.equippedInstances[slot];
|
const instanceId = equippedInstances[slot];
|
||||||
const instance = instanceId ? store.equipmentInstances[instanceId] : null;
|
const instance = instanceId ? equipmentInstances[instanceId] : null;
|
||||||
store.unequipItem(slot);
|
unequipItem(slot, useCombatStore.getState, (fn) => useCombatStore.setState(fn));
|
||||||
showToast('success', 'Item Unequipped', `${instance?.name || 'Item'} removed from ${SLOT_NAMES[slot]}`);
|
showToast('success', 'Item Unequipped', `${instance?.name || 'Item'} removed from ${SLOT_NAMES[slot]}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if a slot is blocked by a 2-handed weapon
|
// Check if a slot is blocked by a 2-handed weapon
|
||||||
const isSlotBlocked = (slot: EquipmentSlot): boolean => {
|
const isSlotBlocked = (slot: EquipmentSlot): boolean => {
|
||||||
if (slot === 'offHand' && store.equippedInstances.mainHand) {
|
if (slot === 'offHand' && equippedInstances.mainHand) {
|
||||||
const mainHandInstance = store.equipmentInstances[store.equippedInstances.mainHand];
|
const mainHandInstance = equipmentInstances[equippedInstances.mainHand];
|
||||||
if (!mainHandInstance) return false;
|
if (!mainHandInstance) return false;
|
||||||
const mainHandType = EQUIPMENT_TYPES[mainHandInstance.typeId];
|
const mainHandType = EQUIPMENT_TYPES[mainHandInstance.typeId];
|
||||||
return mainHandType?.twoHanded === true;
|
return mainHandType?.twoHanded === true;
|
||||||
@@ -189,7 +202,7 @@ export function EquipmentTab({ store }: { store: GameStore }) {
|
|||||||
|
|
||||||
// Check if an instance is currently equipped
|
// Check if an instance is currently equipped
|
||||||
const isEquipped = (instanceId: string): boolean =>
|
const isEquipped = (instanceId: string): boolean =>
|
||||||
Object.values(store.equippedInstances).includes(instanceId);
|
Object.values(equippedInstances).includes(instanceId);
|
||||||
|
|
||||||
// Get all slots an item type can be equipped to
|
// Get all slots an item type can be equipped to
|
||||||
const getEquippableSlots = (typeId: string): EquipmentSlot[] => {
|
const getEquippableSlots = (typeId: string): EquipmentSlot[] => {
|
||||||
@@ -208,12 +221,15 @@ export function EquipmentTab({ store }: { store: GameStore }) {
|
|||||||
|
|
||||||
const confirmDelete = () => {
|
const confirmDelete = () => {
|
||||||
if (deleteConfirm) {
|
if (deleteConfirm) {
|
||||||
store.deleteEquipmentInstance(deleteConfirm.instanceId);
|
deleteEquipmentInstance(deleteConfirm.instanceId, useCombatStore.getState, (fn) => useCombatStore.setState(fn));
|
||||||
showToast('success', 'Item Discarded', `${deleteConfirm.name} has been removed from inventory`);
|
showToast('success', 'Item Discarded', `${deleteConfirm.name} has been removed from inventory`);
|
||||||
setDeleteConfirm(null);
|
setDeleteConfirm(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get unified effects for equipment stats
|
||||||
|
const unifiedEffects = useCombatStore((s) => s.equipmentInstances) ? getUnifiedEffects({ equipmentInstances, equippedInstances }) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4 max-w-full overflow-x-hidden">
|
<div className="space-y-4 max-w-full overflow-x-hidden">
|
||||||
{/* Equipment Slots */}
|
{/* Equipment Slots */}
|
||||||
@@ -222,17 +238,20 @@ export function EquipmentTab({ store }: { store: GameStore }) {
|
|||||||
title="Equipped Gear"
|
title="Equipped Gear"
|
||||||
action={
|
action={
|
||||||
<span className="text-xs text-[var(--text-muted)]">
|
<span className="text-xs text-[var(--text-muted)]">
|
||||||
{Object.values(store.equippedInstances).filter(Boolean).length} / {EQUIPMENT_SLOTS.length} slots filled
|
{Object.values(equippedInstances).filter(Boolean).length} / {EQUIPMENT_SLOTS.length} slots filled
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<EquipmentSlotGrid
|
<EquipmentSlotGrid
|
||||||
store={store}
|
equippedInstances={equippedInstances}
|
||||||
|
equipmentInstances={equipmentInstances}
|
||||||
selectedSlot={selectedSlot}
|
selectedSlot={selectedSlot}
|
||||||
onSlotClick={setSelectedSlot}
|
onSlotClick={setSelectedSlot}
|
||||||
onUnequip={handleUnequip}
|
onUnequip={handleUnequip}
|
||||||
isSlotBlocked={isSlotBlocked}
|
isSlotBlocked={isSlotBlocked}
|
||||||
SLOT_GROUPS={SLOT_GROUPS}
|
SLOT_GROUPS={SLOT_GROUPS}
|
||||||
|
SLOT_NAMES={SLOT_NAMES}
|
||||||
|
SLOT_ICONS={SLOT_ICONS}
|
||||||
/>
|
/>
|
||||||
</GameCard>
|
</GameCard>
|
||||||
|
|
||||||
@@ -247,8 +266,8 @@ export function EquipmentTab({ store }: { store: GameStore }) {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<EquipmentInventory
|
<EquipmentInventory
|
||||||
store={store}
|
|
||||||
unequippedItems={unequippedItems}
|
unequippedItems={unequippedItems}
|
||||||
|
equipmentInstances={equipmentInstances}
|
||||||
onEquip={handleEquip}
|
onEquip={handleEquip}
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
getEquippableSlots={getEquippableSlots}
|
getEquippableSlots={getEquippableSlots}
|
||||||
@@ -264,7 +283,7 @@ export function EquipmentTab({ store }: { store: GameStore }) {
|
|||||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||||||
<div className="p-3 bg-[var(--bg-sunken)]/50 rounded text-center">
|
<div className="p-3 bg-[var(--bg-sunken)]/50 rounded text-center">
|
||||||
<div className="text-2xl font-bold text-[var(--mana-light)] font-[var(--font-mono)]">
|
<div className="text-2xl font-bold text-[var(--mana-light)] font-[var(--font-mono)]">
|
||||||
{Object.values(store.equipmentInstances).length}
|
{Object.values(equipmentInstances).length}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-[var(--text-muted)]">Total Items</div>
|
<div className="text-xs text-[var(--text-muted)]">Total Items</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -282,7 +301,7 @@ export function EquipmentTab({ store }: { store: GameStore }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="p-3 bg-[var(--bg-sunken)]/50 rounded text-center">
|
<div className="p-3 bg-[var(--bg-sunken)]/50 rounded text-center">
|
||||||
<div className="text-2xl font-bold text-[var(--mana-stellar)] font-[var(--font-mono)]">
|
<div className="text-2xl font-bold text-[var(--mana-stellar)] font-[var(--font-mono)]">
|
||||||
{Object.values(store.equipmentInstances).reduce(
|
{Object.values(equipmentInstances).reduce(
|
||||||
(sum, inst) => sum + inst.enchantments.length,
|
(sum, inst) => sum + inst.enchantments.length,
|
||||||
0
|
0
|
||||||
)}
|
)}
|
||||||
@@ -300,8 +319,9 @@ export function EquipmentTab({ store }: { store: GameStore }) {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{(() => {
|
{(() => {
|
||||||
const unifiedEffects = getUnifiedEffects(store);
|
const effects = unifiedEffects;
|
||||||
const enchantPower = unifiedEffects.enchantmentPowerMultiplier || 1;
|
if (!effects) return null;
|
||||||
|
const enchantPower = effects.enchantmentPowerMultiplier || 1;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StatRow
|
<StatRow
|
||||||
@@ -323,13 +343,16 @@ export function EquipmentTab({ store }: { store: GameStore }) {
|
|||||||
<div className="text-sm text-[var(--text-muted)] mb-2">Active Effects from Equipment:</div>
|
<div className="text-sm text-[var(--text-muted)] mb-2">Active Effects from Equipment:</div>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{(() => {
|
{(() => {
|
||||||
const effects = store.getEquipmentEffects();
|
const effects = unifiedEffects;
|
||||||
const effectEntries = Object.entries(effects).filter(([, v]) => v > 0);
|
if (!effects?.equipmentEffects) {
|
||||||
|
return <span className="text-[var(--text-muted)] text-sm">No active effects</span>;
|
||||||
|
}
|
||||||
|
const effectEntries = Object.entries(effects.equipmentEffects).filter(([, v]) => v > 0);
|
||||||
|
|
||||||
if (effectEntries.length === 0) {
|
if (effectEntries.length === 0) {
|
||||||
return <span className="text-[var(--text-muted)] text-sm">No active effects</span>;
|
return <span className="text-[var(--text-muted)] text-sm">No active effects</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return effectEntries.map(([stat, value]) => (
|
return effectEntries.map(([stat, value]) => (
|
||||||
<Badge key={stat} variant="outline" className="text-xs border-[var(--border-default)] text-[var(--text-secondary)]">
|
<Badge key={stat} variant="outline" className="text-xs border-[var(--border-default)] text-[var(--text-secondary)]">
|
||||||
{stat}: +{fmt(value)}
|
{stat}: +{fmt(value)}
|
||||||
|
|||||||
Reference in New Issue
Block a user