refactor: extract components from EquipmentTab.tsx to reduce below 400 lines
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 1m12s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 1m12s
This commit is contained in:
@@ -15,42 +15,13 @@ import { Button } from '@/components/ui/button';
|
||||
import { GameCard } from '@/components/ui/game-card';
|
||||
import { SectionHeader } from '@/components/ui/section-header';
|
||||
import { StatRow } from '@/components/ui/stat-row';
|
||||
import { ActionButton } from '@/components/ui/action-button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import {
|
||||
Sword,
|
||||
Shield,
|
||||
ShieldOff,
|
||||
Shirt,
|
||||
Hand,
|
||||
Footprints,
|
||||
Gem,
|
||||
X,
|
||||
AlertCircle,
|
||||
Info,
|
||||
ChevronDown,
|
||||
HardHat,
|
||||
} from 'lucide-react';
|
||||
import type { GameStore, EquipmentInstance } from '@/lib/game/types';
|
||||
import { EquipmentSlotGrid } from './EquipmentSlotGrid';
|
||||
import { EquipmentInventory } from './EquipmentInventory';
|
||||
import { EnchantmentsPanel } from './EnchantmentsPanel';
|
||||
import { useGameToast } from '@/components/game/GameToast';
|
||||
import { ConfirmDialog } from '@/components/game/ConfirmDialog';
|
||||
import type { GameStore, EquipmentInstance } from '@/lib/game/types';
|
||||
|
||||
export interface EquipmentTabProps {
|
||||
store: GameStore;
|
||||
}
|
||||
|
||||
// Slot display names
|
||||
const SLOT_NAMES: Record<EquipmentSlot, string> = {
|
||||
@@ -65,7 +36,7 @@ const SLOT_NAMES: Record<EquipmentSlot, string> = {
|
||||
};
|
||||
|
||||
// Rarity color mappings using design system tokens
|
||||
const RARITY_BORDER_COLORS: Record<string, string> = {
|
||||
export const RARITY_BORDER_COLORS: Record<string, string> = {
|
||||
common: 'border-[var(--text-muted)]',
|
||||
uncommon: 'border-[var(--color-success)]',
|
||||
rare: 'border-[var(--mana-water)]',
|
||||
@@ -74,7 +45,7 @@ const RARITY_BORDER_COLORS: Record<string, string> = {
|
||||
mythic: 'border-[var(--mana-dark)]',
|
||||
};
|
||||
|
||||
const RARITY_BG_COLORS: Record<string, string> = {
|
||||
export const RARITY_BG_COLORS: Record<string, string> = {
|
||||
common: 'bg-[var(--bg-sunken)]/30',
|
||||
uncommon: 'bg-[var(--color-success)]/10',
|
||||
rare: 'bg-[var(--mana-water)]/10',
|
||||
@@ -83,7 +54,7 @@ const RARITY_BG_COLORS: Record<string, string> = {
|
||||
mythic: 'bg-[var(--mana-dark)]/10',
|
||||
};
|
||||
|
||||
const RARITY_TEXT_COLORS: Record<string, string> = {
|
||||
export const RARITY_TEXT_COLORS: Record<string, string> = {
|
||||
common: 'text-[var(--text-secondary)]',
|
||||
uncommon: 'text-[var(--color-success)]',
|
||||
rare: 'text-[var(--mana-water)]',
|
||||
@@ -92,19 +63,57 @@ const RARITY_TEXT_COLORS: Record<string, string> = {
|
||||
mythic: 'text-[var(--mana-dark)]',
|
||||
};
|
||||
|
||||
// Slot icon mapping using Lucide icons
|
||||
const SLOT_ICONS: Record<EquipmentSlot, React.ElementType> = {
|
||||
mainHand: Sword,
|
||||
offHand: Shield,
|
||||
head: HardHat,
|
||||
body: Shirt,
|
||||
hands: Hand,
|
||||
feet: Footprints,
|
||||
accessory1: Gem,
|
||||
accessory2: Gem,
|
||||
mainHand: () => (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M14.5 4H5.5L2 10v10a2 2 0 002 2h16a2 2 0 002-2V10l-3.5-6z" />
|
||||
<line x1="6" y1="10" x2="18" y2="10" />
|
||||
</svg>
|
||||
),
|
||||
offHand: () => (
|
||||
<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" />
|
||||
</svg>
|
||||
),
|
||||
head: () => (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" />
|
||||
<path d="M14 2v6h6M10 14l2 2 4-4" />
|
||||
</svg>
|
||||
),
|
||||
body: () => (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M20.38 8.5c.6-1.4 1.3-4.9-1.2-8.5-1.1-1.8-3.6-1.8-4.7 0-2.5 3.6-1.9 7.1-1.2 8.5.7 1.4 2.5 1.9 4 1.9 1.5 0 3.3-.5 4-1.9z" />
|
||||
<path d="M12 8v7M8 18h8" />
|
||||
</svg>
|
||||
),
|
||||
hands: () => (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M18 11V6a2 2 0 00-2-2h-2a2 2 0 00-2 2v5M6 11v6a2 2 0 002 2h5a2 2 0 002-2v-6" />
|
||||
<path d="M10 15V7a2 2 0 012-2h2a2 2 0 012 2v8" />
|
||||
</svg>
|
||||
),
|
||||
feet: () => (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M12 2v20M5 13a4 4 0 000 8h14a4 4 0 000-8" />
|
||||
<path d="M9 13V5a2 2 0 012-2h2a2 2 0 012 2v8" />
|
||||
</svg>
|
||||
),
|
||||
accessory1: () => (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<circle cx="12" cy="12" r="8" />
|
||||
<path d="M12 4v8l2 4" />
|
||||
</svg>
|
||||
),
|
||||
accessory2: () => (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<circle cx="12" cy="12" r="8" />
|
||||
<path d="M12 4v8l2 4" />
|
||||
</svg>
|
||||
),
|
||||
};
|
||||
|
||||
// Slot grouping for visual layout - requirement: visual slot layout
|
||||
// Slot grouping for visual layout
|
||||
type SlotGroup = {
|
||||
label: string;
|
||||
slots: EquipmentSlot[];
|
||||
@@ -116,7 +125,7 @@ const SLOT_GROUPS: SlotGroup[] = [
|
||||
{ label: 'Accessories', slots: ['accessory1', 'accessory2'] },
|
||||
];
|
||||
|
||||
export function EquipmentTab({ store }: EquipmentTabProps) {
|
||||
export function EquipmentTab({ store }: { store: GameStore }) {
|
||||
const showToast = useGameToast();
|
||||
const [selectedSlot, setSelectedSlot] = useState<EquipmentSlot | null>(null);
|
||||
const [deleteConfirm, setDeleteConfirm] = useState<{ instanceId: string; name: string } | null>(null);
|
||||
@@ -147,18 +156,10 @@ export function EquipmentTab({ store }: EquipmentTabProps) {
|
||||
const instanceId = store.equippedInstances[slot];
|
||||
const instance = instanceId ? store.equipmentInstances[instanceId] : null;
|
||||
store.unequipItem(slot);
|
||||
showToast('success', 'Item Unequipped', `${instance?.name || 'Item'} removed from ${SLOT_NAMES[slot]}`);
|
||||
showToast('success', 'Item Unequipped', `${instance?.name || 'Item'} removed from ${SLOT_NAMES[slot]}`);
|
||||
};
|
||||
|
||||
// Get items that can be equipped in a slot
|
||||
const getEquippableItems = (slot: EquipmentSlot): EquipmentInstance[] => {
|
||||
const equipmentTypes = getEquipmentBySlot(slot);
|
||||
const typeIds = new Set(equipmentTypes.map((t) => t.id));
|
||||
|
||||
return unequippedItems.filter((inst) => typeIds.has(inst.typeId));
|
||||
};
|
||||
|
||||
// Check if a slot is blocked by a 2-handed weapon (task3 bug #6)
|
||||
// Check if a slot is blocked by a 2-handed weapon
|
||||
const isSlotBlocked = (slot: EquipmentSlot): boolean => {
|
||||
if (slot === 'offHand' && store.equippedInstances.mainHand) {
|
||||
const mainHandInstance = store.equipmentInstances[store.equippedInstances.mainHand];
|
||||
@@ -169,6 +170,13 @@ showToast('success', 'Item Unequipped', `${instance?.name || 'Item'} removed fro
|
||||
return false;
|
||||
};
|
||||
|
||||
// Get items that can be equipped in a slot
|
||||
const getEquippableItems = (slot: EquipmentSlot): EquipmentInstance[] => {
|
||||
const equipmentTypes = getEquipmentBySlot(slot);
|
||||
const typeIds = new Set(equipmentTypes.map((t) => t.id));
|
||||
return unequippedItems.filter((inst) => typeIds.has(inst.typeId));
|
||||
};
|
||||
|
||||
// Get all items that can go in a slot
|
||||
const getItemsForSlot = (slot: EquipmentSlot): EquipmentInstance[] => {
|
||||
if (isSlotBlocked(slot)) return [];
|
||||
@@ -177,7 +185,6 @@ showToast('success', 'Item Unequipped', `${instance?.name || 'Item'} removed fro
|
||||
const accessoryTypeIds = Object.values(EQUIPMENT_TYPES)
|
||||
.filter((t) => t.category === 'accessory')
|
||||
.map((t) => t.id);
|
||||
|
||||
return unequippedItems.filter((inst) => accessoryTypeIds.includes(inst.typeId));
|
||||
}
|
||||
|
||||
@@ -191,145 +198,36 @@ showToast('success', 'Item Unequipped', `${instance?.name || 'Item'} removed fro
|
||||
return getEquippableItems(slot);
|
||||
};
|
||||
|
||||
// Render a single equipment slot
|
||||
const renderSlot = (slot: EquipmentSlot) => {
|
||||
const instanceId = store.equippedInstances[slot];
|
||||
const instance = instanceId ? store.equipmentInstances[instanceId] : null;
|
||||
const equipmentType = instance ? EQUIPMENT_TYPES[instance.typeId] : null;
|
||||
const blocked = isSlotBlocked(slot);
|
||||
const isEmpty = !instance;
|
||||
const SlotIcon = SLOT_ICONS[slot];
|
||||
// Check if an instance is currently equipped
|
||||
const isEquipped = (instanceId: string): boolean =>
|
||||
Object.values(store.equippedInstances).includes(instanceId);
|
||||
|
||||
const slotContent = (
|
||||
<GameCard
|
||||
variant={blocked ? 'danger' : instance ? 'default' : 'sunken'}
|
||||
className={`relative transition-all duration-200
|
||||
${isEmpty && !blocked ? 'border-dashed' : ''}
|
||||
${blocked ? 'opacity-60 cursor-not-allowed' : 'hover:border-[var(--border-default)]'}
|
||||
`}
|
||||
role="button"
|
||||
aria-label={`${SLOT_NAMES[slot]} slot${blocked ? ' (blocked by 2-handed weapon)' : ''}${instance ? `: ${instance.name}` : ' (empty)'}`}
|
||||
tabIndex={blocked ? -1 : 0}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<SlotIcon
|
||||
size={16}
|
||||
className={blocked ? 'text-[var(--text-disabled)]' : 'text-[var(--text-secondary)]'}
|
||||
/>
|
||||
<span
|
||||
className={`text-sm font-semibold
|
||||
${blocked ? 'text-[var(--text-disabled)]' : 'text-[var(--text-primary)]'}
|
||||
`}
|
||||
>
|
||||
{SLOT_NAMES[slot]}
|
||||
</span>
|
||||
{blocked && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="text-xs border-[var(--mana-dark)] text-[var(--mana-dark)] ml-2"
|
||||
>
|
||||
<AlertCircle size={12} className="mr-1" />
|
||||
Occupied — 2H Weapon
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{instance && !blocked && (
|
||||
<ActionButton
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0 text-[var(--color-danger)] hover:text-[var(--interactive-danger-hover)] hover:bg-[var(--interactive-danger)]/20"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleUnequip(slot);
|
||||
}}
|
||||
aria-label={`Unequip ${instance.name}`}
|
||||
>
|
||||
<X size={14} />
|
||||
</ActionButton>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{instance ? (
|
||||
<div className="space-y-1">
|
||||
<div className={`font-semibold text-sm ${RARITY_TEXT_COLORS[instance.rarity] || 'text-[var(--text-primary)]'}`}>
|
||||
{instance.name}
|
||||
{equipmentType?.twoHanded && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="ml-2 text-xs border-[var(--mana-light)] text-[var(--mana-light)]"
|
||||
>
|
||||
2-Handed
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-[var(--text-secondary)]">
|
||||
Enchantments: {instance.enchantments.length}/{instance.totalCapacity}
|
||||
</div>
|
||||
{instance.enchantments.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
{instance.enchantments.map((ench, i) => {
|
||||
const effect = ENCHANTMENT_EFFECTS[ench.effectId];
|
||||
return (
|
||||
<TooltipProvider key={i}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="text-xs cursor-help border-[var(--border-default)] text-[var(--text-secondary)]"
|
||||
>
|
||||
{effect?.name || ench.effectId}
|
||||
{ench.stacks > 1 && ` x${ench.stacks}`}
|
||||
</Badge>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="bg-[var(--bg-elevated)] border-[var(--border-default)] text-[var(--text-primary)]">
|
||||
<p>{effect?.description || 'Unknown effect'}</p>
|
||||
<p className="text-[var(--text-muted)] text-xs">
|
||||
Category: {effect?.category || 'unknown'}
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : blocked ? (
|
||||
<div className="text-sm text-[var(--text-disabled)] italic">
|
||||
<AlertCircle size={14} className="inline mr-1" />
|
||||
Blocked by 2-handed weapon
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm text-[var(--text-muted)] italic text-center py-2">
|
||||
{SLOT_NAMES[slot]}
|
||||
</div>
|
||||
)}
|
||||
</GameCard>
|
||||
);
|
||||
|
||||
if (blocked) {
|
||||
return (
|
||||
<TooltipProvider key={slot}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
{slotContent}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="bg-[var(--bg-elevated)] border-[var(--border-default)] text-[var(--text-primary)]">
|
||||
<p>The offhand slot is blocked because a 2-handed weapon is equipped in the main hand.</p>
|
||||
<p className="text-[var(--text-muted)] text-xs mt-1">Unequip the 2-handed weapon to use this slot.</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
// Get all slots an item type can be equipped to
|
||||
const getEquippableSlots = (typeId: string): EquipmentSlot[] => {
|
||||
const equipmentType = EQUIPMENT_TYPES[typeId];
|
||||
if (!equipmentType) return [];
|
||||
if (equipmentType.category === 'accessory') {
|
||||
return ['accessory1', 'accessory2'];
|
||||
}
|
||||
return [equipmentType.slot];
|
||||
};
|
||||
|
||||
return <div key={slot}>{slotContent}</div>;
|
||||
// Handle item deletion
|
||||
const handleDelete = (instanceId: string, name: string) => {
|
||||
setDeleteConfirm({ instanceId, name });
|
||||
};
|
||||
|
||||
const confirmDelete = () => {
|
||||
if (deleteConfirm) {
|
||||
store.deleteEquipmentInstance(deleteConfirm.instanceId);
|
||||
showToast('success', 'Item Discarded', `${deleteConfirm.name} has been removed from inventory`);
|
||||
setDeleteConfirm(null);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 max-w-full overflow-x-hidden">
|
||||
{/* Equipment Slots - Requirement: Visual slot layout */}
|
||||
{/* Equipment Slots */}
|
||||
<GameCard variant="default">
|
||||
<SectionHeader
|
||||
title="Equipped Gear"
|
||||
@@ -339,27 +237,17 @@ showToast('success', 'Item Unequipped', `${instance?.name || 'Item'} removed fro
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<div className="space-y-6">
|
||||
{/* Render slot groups */}
|
||||
{SLOT_GROUPS.map((group) => (
|
||||
<div key={group.label}>
|
||||
<h4 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-2">
|
||||
{group.label}
|
||||
</h4>
|
||||
<div className={`grid gap-3
|
||||
/* Mobile: 2 columns for all groups - requirement: mobile layout */
|
||||
grid-cols-2
|
||||
/* Tablet and up */
|
||||
${group.slots.includes('mainHand' as EquipmentSlot) ? 'sm:grid-cols-2' : 'sm:grid-cols-2 lg:grid-cols-4'}
|
||||
`}>
|
||||
{group.slots.map((slot) => renderSlot(slot))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<EquipmentSlotGrid
|
||||
store={store}
|
||||
selectedSlot={selectedSlot}
|
||||
onSlotClick={setSelectedSlot}
|
||||
onUnequip={handleUnequip}
|
||||
isSlotBlocked={isSlotBlocked}
|
||||
SLOT_GROUPS={SLOT_GROUPS}
|
||||
/>
|
||||
</GameCard>
|
||||
|
||||
{/* Inventory */}
|
||||
{/* Equipment Inventory */}
|
||||
<GameCard variant="default">
|
||||
<SectionHeader
|
||||
title={`Equipment Inventory (${unequippedItems.length} items)`}
|
||||
@@ -369,115 +257,15 @@ showToast('success', 'Item Unequipped', `${instance?.name || 'Item'} removed fro
|
||||
No unequipped items. Craft new gear in the Crafting tab.
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 max-h-96 overflow-y-auto">
|
||||
{unequippedItems.map((instance) => {
|
||||
const equipmentType = EQUIPMENT_TYPES[instance.typeId];
|
||||
const validSlots = equipmentType
|
||||
? (equipmentType.category === 'accessory'
|
||||
? ['accessory1', 'accessory2'] as EquipmentSlot[]
|
||||
: [equipmentType.slot])
|
||||
: [];
|
||||
|
||||
return (
|
||||
<GameCard
|
||||
key={instance.instanceId}
|
||||
variant="default"
|
||||
className={`${RARITY_BORDER_COLORS[instance.rarity] || 'border-[var(--border-default)]'} ${RARITY_BG_COLORS[instance.rarity] || ''}`}
|
||||
>
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<div>
|
||||
<div className={`font-semibold text-sm ${RARITY_TEXT_COLORS[instance.rarity] || 'text-[var(--text-primary)]'}`}>
|
||||
{instance.name}
|
||||
</div>
|
||||
<div className="text-xs text-[var(--text-muted)]">
|
||||
{equipmentType?.description}
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="outline" className="text-xs border-[var(--border-default)] text-[var(--text-secondary)]">
|
||||
{equipmentType?.category || 'unknown'}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-[var(--text-muted)] space-y-1 mb-2">
|
||||
<div>
|
||||
Capacity: {instance.usedCapacity}/{instance.totalCapacity}
|
||||
{instance.quality < 100 && (
|
||||
<span className="text-[var(--mana-light)] ml-1">
|
||||
(Quality: {instance.quality}%)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{instance.enchantments.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{instance.enchantments.map((ench, i) => {
|
||||
const effect = ENCHANTMENT_EFFECTS[ench.effectId];
|
||||
return (
|
||||
<Badge
|
||||
key={i}
|
||||
variant="outline"
|
||||
className="text-xs border-[var(--border-default)] text-[var(--text-secondary)]"
|
||||
>
|
||||
{effect?.name || ench.effectId}
|
||||
</Badge>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{validSlots.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Select
|
||||
onValueChange={(value) =>
|
||||
handleEquip(instance.instanceId, value as EquipmentSlot)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs bg-[var(--bg-sunken)] border-[var(--border-default)]">
|
||||
<SelectValue placeholder="Equip to..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-[var(--bg-elevated)] border-[var(--border-default)]">
|
||||
{validSlots.map((slot) => (
|
||||
<SelectItem
|
||||
key={slot}
|
||||
value={slot}
|
||||
className="text-xs text-[var(--text-primary)] focus:bg-[var(--bg-sunken)]"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{(() => {
|
||||
const Icon = SLOT_ICONS[slot];
|
||||
return <Icon size={14} />;
|
||||
})()}
|
||||
{SLOT_NAMES[slot]}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<ActionButton
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 p-0 text-[var(--color-danger)] hover:text-[var(--interactive-danger-hover)] hover:bg-[var(--interactive-danger)]/20"
|
||||
onClick={() => setDeleteConfirm({ instanceId: instance.instanceId, name: instance.name })}
|
||||
aria-label={`Delete ${instance.name}`}
|
||||
>
|
||||
<X size={14} />
|
||||
</ActionButton>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="bg-[var(--bg-elevated)] border-[var(--border-default)] text-[var(--text-primary)]">
|
||||
<p>Delete this item</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
)}
|
||||
</GameCard>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<EquipmentInventory
|
||||
store={store}
|
||||
unequippedItems={unequippedItems}
|
||||
onEquip={handleEquip}
|
||||
onDelete={handleDelete}
|
||||
getEquippableSlots={getEquippableSlots}
|
||||
SLOT_NAMES={SLOT_NAMES}
|
||||
SLOT_ICONS={SLOT_ICONS}
|
||||
/>
|
||||
)}
|
||||
</GameCard>
|
||||
|
||||
@@ -540,7 +328,7 @@ showToast('success', 'Item Unequipped', `${instance?.name || 'Item'} removed fro
|
||||
})()}
|
||||
</div>
|
||||
</GameCard>
|
||||
|
||||
|
||||
{/* Active Effects from Equipment */}
|
||||
<div className="mt-4">
|
||||
<div className="text-sm text-[var(--text-muted)] mb-2">Active Effects from Equipment:</div>
|
||||
@@ -564,21 +352,15 @@ showToast('success', 'Item Unequipped', `${instance?.name || 'Item'} removed fro
|
||||
</GameCard>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
{deleteConfirm && (
|
||||
<ConfirmDialog
|
||||
open={!!deleteConfirm}
|
||||
onOpenChange={() => setDeleteConfirm(null)}
|
||||
title="Discard Item?"
|
||||
description={`Discard ${deleteConfirm.name}? This cannot be undone.`}
|
||||
variant="danger"
|
||||
confirmText="Discard"
|
||||
onConfirm={() => {
|
||||
store.deleteEquipmentInstance(deleteConfirm.instanceId);
|
||||
showToast('success', 'Item Discarded', `${deleteConfirm.name} has been removed from inventory`);
|
||||
setDeleteConfirm(null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<ConfirmDialog
|
||||
open={!!deleteConfirm}
|
||||
onOpenChange={() => setDeleteConfirm(null)}
|
||||
title="Discard Item?"
|
||||
description={`Discard ${deleteConfirm?.name}? This cannot be undone.`}
|
||||
variant="danger"
|
||||
confirmText="Discard"
|
||||
onConfirm={confirmDelete}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user