/** * @file Crafting panel component for crafting items from materials. * @copyright nhcarrigan * @license Naomi's Public License * @author Naomi Carrigan */ /* eslint-disable max-lines-per-function -- Complex component with many render paths */ /* eslint-disable max-nested-callbacks -- Nested recipe/material maps require nesting */ import { type JSX, useState } from "react"; import { useGame } from "../../context/gameContext.js"; import { MATERIALS } from "../../data/materials.js"; import { RECIPES } from "../../data/recipes.js"; import { cdnImage } from "../../utils/cdn.js"; import { ZoneSelector } from "./zoneSelector.js"; const bonusLabel: Record = { click_power: "👆 Click Power", combat_power: "⚔️ Combat Power", essence_income: "✨ Essence Income", gold_income: "🪙 Gold Income", }; /** * Renders the crafting panel for crafting recipes from gathered materials. * @returns The JSX element. */ const CraftingPanel = (): JSX.Element => { const { state, craftRecipe, formatNumber } = useGame(); const [ activeZoneId, setActiveZoneId ] = useState(() => { return sessionStorage.getItem("elysium_craft_zone") ?? "verdant_vale"; }); const [ pendingRecipeId, setPendingRecipeId ] = useState(null); if (state === null) { return (

{"Loading..."}

); } const { zones, exploration: explorationState } = state; const playerMaterials = explorationState?.materials ?? []; const craftedIds = explorationState?.craftedRecipeIds ?? []; const zoneRecipes = RECIPES.filter((recipe) => { return recipe.zoneId === activeZoneId; }); const zoneMaterials = MATERIALS.filter((material) => { return material.zoneId === activeZoneId; }); function getQuantity(materialId: string): number { return ( playerMaterials.find((playerMaterial) => { return playerMaterial.materialId === materialId; })?.quantity ?? 0 ); } function canAffordRecipe(recipeId: string): boolean { const recipe = RECIPES.find((candidateRecipe) => { return candidateRecipe.id === recipeId; }); if (recipe === undefined) { return false; } return recipe.requiredMaterials.every((request) => { return getQuantity(request.materialId) >= request.quantity; }); } function handleZoneSelect(zoneId: string): void { setActiveZoneId(zoneId); sessionStorage.setItem("elysium_craft_zone", zoneId); } async function handleCraft(recipeId: string): Promise { setPendingRecipeId(recipeId); try { await craftRecipe(recipeId); } finally { setPendingRecipeId(null); } } return (

{"⚗️ Crafting"}

{"📦 Materials"}

{zoneMaterials.length === 0 ?

{"No materials in this zone."}

:
{zoneMaterials.map((material) => { const qty = getQuantity(material.id); return (
{material.name}
{material.name} {material.rarity}
{formatNumber(qty)}
); })}
}

{"📜 Recipes"}

{zoneRecipes.length === 0 ?

{"No recipes in this zone."}

:
{zoneRecipes.map((recipe) => { const crafted = craftedIds.includes(recipe.id); const affordable = canAffordRecipe(recipe.id); const isPending = pendingRecipeId === recipe.id; function handleCraftClick(): void { void handleCraft(recipe.id); } return (
{recipe.name}

{recipe.name}

{recipe.description}

{bonusLabel[recipe.bonus.type] ?? recipe.bonus.type} {"×"} {recipe.bonus.value.toFixed(2)}
{recipe.requiredMaterials.map((request) => { const have = getQuantity(request.materialId); const enough = have >= request.quantity; const matName = MATERIALS.find((mat) => { return mat.id === request.materialId; })?.name ?? request.materialId; return ( {matName} {": "} {formatNumber(have)} {"/"} {formatNumber(request.quantity)} ); })}
{crafted ? {"✅ Crafted"} : }
); })}
}
); }; export { CraftingPanel };