262 lines
11 KiB
TypeScript
Executable File
262 lines
11 KiB
TypeScript
Executable File
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { Progress } from '@/components/ui/progress';
|
|
import { GameCard } from '@/components/ui/game-card';
|
|
import { SectionHeader } from '@/components/ui/section-header';
|
|
import { ActionButton } from '@/components/ui/action-button';
|
|
import { Scroll, Hammer, Sparkles, Anvil } from 'lucide-react';
|
|
import { fmt } from '@/lib/game/stores';
|
|
import {
|
|
EnchantmentDesigner,
|
|
EnchantmentPreparer,
|
|
EnchantmentApplier,
|
|
EquipmentCrafter,
|
|
} from '@/components/game/crafting';
|
|
import { useCombatStore, useCraftingStore } from '@/lib/game/stores';
|
|
import { DebugName } from '@/lib/game/debug-context';
|
|
import { useGameToast } from '@/components/game/GameToast';
|
|
|
|
export function CraftingTab() {
|
|
const showToast = useGameToast();
|
|
const currentAction = useCombatStore((s) => s.currentAction);
|
|
const designProgress = useCraftingStore((s) => s.designProgress);
|
|
const preparationProgress = useCraftingStore((s) => s.preparationProgress);
|
|
const applicationProgress = useCraftingStore((s) => s.applicationProgress);
|
|
const equipmentCraftingProgress = useCraftingStore((s) => s.equipmentCraftingProgress);
|
|
const pauseApplication = useCraftingStore((s) => s.pauseApplication);
|
|
const resumeApplication = useCraftingStore((s) => s.resumeApplication);
|
|
|
|
const [activeTab, setActiveTab] = useState<'fabricate' | 'enchant'>('fabricate');
|
|
const [enchantStage, setEnchantStage] = useState<'design' | 'prepare' | 'apply'>('design');
|
|
|
|
// Safe toFixed helper
|
|
const safeToFixed = (value: number | undefined, decimals: number = 0): string => {
|
|
if (value === undefined || isNaN(value)) return '0';
|
|
return value.toFixed(decimals);
|
|
};
|
|
|
|
// Safe percentage calculation
|
|
const calcPercent = (progress: number, required: number): number => {
|
|
if (!required || required === 0) return 0;
|
|
return (progress / required) * 100;
|
|
};
|
|
|
|
// Handle enchantment application with toast
|
|
const handleEnchantmentApplied = () => {
|
|
showToast('success', 'Enchantment Applied', 'The enchantment has been successfully applied!');
|
|
};
|
|
|
|
// Handle enchantment capacity exceeded
|
|
const handleCapacityExceeded = (itemName: string, used: number, total: number) => {
|
|
showToast('error', 'Enchantment Capacity Exceeded', `${itemName} can only hold ${total} enchantments (${used}/${total} used). Remove some enchantments first.`);
|
|
};
|
|
|
|
return (
|
|
<DebugName name="CraftingTab">
|
|
<div className="space-y-4 max-w-full overflow-x-hidden">
|
|
{/* Top Sub-Tabs: Fabricate / Enchant */}
|
|
<GameCard variant="default" className="p-4">
|
|
<div className="flex justify-center gap-2">
|
|
<ActionButton
|
|
variant={activeTab === 'fabricate' ? 'primary' : 'secondary'}
|
|
onClick={() => setActiveTab('fabricate')}
|
|
className={activeTab === 'fabricate' ? 'ring-2 ring-[var(--interactive-primary)]' : ''}
|
|
>
|
|
<Anvil size={14} className="mr-1" />
|
|
Fabricate
|
|
</ActionButton>
|
|
<ActionButton
|
|
variant={activeTab === 'enchant' ? 'primary' : 'secondary'}
|
|
onClick={() => setActiveTab('enchant')}
|
|
className={activeTab === 'enchant' ? 'ring-2 ring-[var(--interactive-primary)]' : ''}
|
|
>
|
|
<Sparkles size={14} className="mr-1" />
|
|
Enchant
|
|
</ActionButton>
|
|
</div>
|
|
</GameCard>
|
|
|
|
{/* Fabricate Content: EquipmentCrafter */}
|
|
{activeTab === 'fabricate' && (
|
|
<EquipmentCrafter />
|
|
)}
|
|
|
|
{/* Enchant Content: Design → Prepare → Apply workflow */}
|
|
{activeTab === 'enchant' && (
|
|
<div className="space-y-4">
|
|
{/* Enchant Sub-Navigation (no numbered stepper) */}
|
|
<GameCard variant="default" className="p-4">
|
|
<div className="flex justify-center gap-2 flex-wrap">
|
|
<ActionButton
|
|
variant={enchantStage === 'design' ? 'primary' : 'secondary'}
|
|
size="sm"
|
|
onClick={() => setEnchantStage('design')}
|
|
className={enchantStage === 'design' ? 'ring-2 ring-[var(--interactive-primary)]' : ''}
|
|
>
|
|
<Scroll size={14} className="mr-1" />
|
|
Design
|
|
</ActionButton>
|
|
<ActionButton
|
|
variant={enchantStage === 'prepare' ? 'primary' : 'secondary'}
|
|
size="sm"
|
|
onClick={() => setEnchantStage('prepare')}
|
|
className={enchantStage === 'prepare' ? 'ring-2 ring-[var(--interactive-primary)]' : ''}
|
|
>
|
|
<Hammer size={14} className="mr-1" />
|
|
Prepare
|
|
</ActionButton>
|
|
<ActionButton
|
|
variant={enchantStage === 'apply' ? 'primary' : 'secondary'}
|
|
size="sm"
|
|
onClick={() => setEnchantStage('apply')}
|
|
className={enchantStage === 'apply' ? 'ring-2 ring-[var(--interactive-primary)]' : ''}
|
|
>
|
|
<Sparkles size={14} className="mr-1" />
|
|
Apply
|
|
</ActionButton>
|
|
</div>
|
|
</GameCard>
|
|
|
|
{/* Enchant Stage Content */}
|
|
{enchantStage === 'design' && (
|
|
<EnchantmentDesigner
|
|
selectedEquipmentType={null}
|
|
setSelectedEquipmentType={() => {}}
|
|
selectedEffects={[]}
|
|
setSelectedEffects={() => {}}
|
|
designName={''}
|
|
setDesignName={() => {}}
|
|
selectedDesign={null}
|
|
setSelectedDesign={() => {}}
|
|
/>
|
|
)}
|
|
{enchantStage === 'prepare' && (
|
|
<EnchantmentPreparer
|
|
selectedEquipmentInstance={null}
|
|
setSelectedEquipmentInstance={() => {}}
|
|
/>
|
|
)}
|
|
{enchantStage === 'apply' && (
|
|
<EnchantmentApplier
|
|
selectedEquipmentInstance={null}
|
|
setSelectedEquipmentInstance={() => {}}
|
|
selectedDesign={null}
|
|
setSelectedDesign={() => {}}
|
|
onEnchantmentApplied={handleEnchantmentApplied}
|
|
onCapacityExceeded={handleCapacityExceeded}
|
|
/>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Current Activity Indicator: Crafting */}
|
|
{currentAction === 'craft' && equipmentCraftingProgress && (
|
|
<GameCard variant="default" className="border-[var(--mana-water)]/60 bg-[var(--mana-water)]/10">
|
|
<SectionHeader
|
|
title="Crafting Equipment"
|
|
action={
|
|
<span className="text-sm text-[var(--text-muted)]">
|
|
{safeToFixed(calcPercent(equipmentCraftingProgress.progress, equipmentCraftingProgress.required), 0)}%
|
|
</span>
|
|
}
|
|
/>
|
|
<Progress
|
|
value={calcPercent(equipmentCraftingProgress.progress, equipmentCraftingProgress.required)}
|
|
className="h-3 bg-[var(--bg-sunken)]"
|
|
/>
|
|
<div className="flex items-center gap-2 mt-2 text-sm text-[var(--text-secondary)]">
|
|
<Anvil size={16} className="text-[var(--mana-water)]" />
|
|
<span>Crafting equipment...</span>
|
|
</div>
|
|
</GameCard>
|
|
)}
|
|
|
|
{/* Current Activity Indicator: Designing */}
|
|
{currentAction === 'design' && designProgress && (
|
|
<GameCard variant="default" className="border-[var(--mana-stellar)]/60 bg-[var(--mana-stellar)]/10">
|
|
<SectionHeader
|
|
title="Designing Enchantment"
|
|
action={
|
|
<ActionButton variant="ghost" size="sm" onClick={() => useCraftingStore.getState().cancelDesign()}>
|
|
Cancel
|
|
</ActionButton>
|
|
}
|
|
/>
|
|
<Progress
|
|
value={calcPercent(designProgress.progress, designProgress.required)}
|
|
className="h-3 bg-[var(--bg-sunken)]"
|
|
/>
|
|
<div className="flex items-center gap-2 mt-2 text-sm text-[var(--text-secondary)]">
|
|
<Scroll size={16} className="text-[var(--mana-stellar)]" />
|
|
<span>Designing: {designProgress.name}</span>
|
|
</div>
|
|
</GameCard>
|
|
)}
|
|
|
|
{/* Current Activity Indicator: Preparing */}
|
|
{currentAction === 'prepare' && preparationProgress && (
|
|
<GameCard variant="default" className="border-[var(--color-warning)]/60 bg-[var(--color-warning)]/10">
|
|
<SectionHeader
|
|
title="Preparing Equipment"
|
|
action={
|
|
<ActionButton variant="ghost" size="sm" onClick={() => useCraftingStore.getState().cancelPreparation()}>
|
|
Cancel
|
|
</ActionButton>
|
|
}
|
|
/>
|
|
<Progress
|
|
value={calcPercent(preparationProgress.progress, preparationProgress.required)}
|
|
className="h-3 bg-[var(--bg-sunken)]"
|
|
/>
|
|
<div className="flex items-center gap-2 mt-2 text-sm text-[var(--text-secondary)]">
|
|
<Hammer size={16} className="text-[var(--color-warning)]" />
|
|
<span>Preparing equipment...</span>
|
|
<span className="text-[var(--text-muted)] ml-auto">
|
|
Mana paid: {fmt(preparationProgress.manaCostPaid)}
|
|
</span>
|
|
</div>
|
|
</GameCard>
|
|
)}
|
|
|
|
{/* Current Activity Indicator: Enchanting */}
|
|
{currentAction === 'enchant' && applicationProgress && (
|
|
<GameCard variant="default" className="border-[var(--mana-light)]/60 bg-[var(--mana-light)]/10">
|
|
<SectionHeader
|
|
title={applicationProgress.paused ? "Enchantment Paused" : "Applying Enchantment"}
|
|
action={
|
|
<div className="flex gap-2">
|
|
{applicationProgress.paused ? (
|
|
<ActionButton size="sm" onClick={resumeApplication}>Resume</ActionButton>
|
|
) : (
|
|
<>
|
|
<ActionButton variant="secondary" size="sm" onClick={pauseApplication}>Pause</ActionButton>
|
|
<ActionButton variant="ghost" size="sm" onClick={() => {
|
|
useCraftingStore.getState().cancelApplication();
|
|
showToast('warning', 'Enchantment Cancelled', 'The enchantment application was cancelled.');
|
|
}}>Cancel</ActionButton>
|
|
</>
|
|
)}
|
|
</div>
|
|
}
|
|
/>
|
|
<Progress
|
|
value={calcPercent(applicationProgress.progress, applicationProgress.required)}
|
|
className="h-3 bg-[var(--bg-sunken)]"
|
|
/>
|
|
<div className="flex items-center gap-2 mt-2 text-sm text-[var(--text-secondary)]">
|
|
<Sparkles size={16} className="text-[var(--mana-light)]" />
|
|
<span>{applicationProgress.paused ? 'Enchantment paused' : 'Applying enchantment...'}</span>
|
|
<span className="text-[var(--text-muted)] ml-auto">
|
|
{safeToFixed(calcPercent(applicationProgress.progress, applicationProgress.required), 0)}%
|
|
</span>
|
|
</div>
|
|
</GameCard>
|
|
)}
|
|
</div>
|
|
</DebugName>
|
|
);
|
|
}
|
|
|
|
CraftingTab.displayName = 'CraftingTab';
|