import { useCallback, useEffect, useState } from 'react'; import { api } from '../api/api'; import { ApiError } from '../api/http'; import type { InventoryResponse, MarketEntry, MarketResponse } from '../api/types'; import { useAuth } from '../auth/AuthContext'; import { useToast } from '../components/Toast'; import { getSocket } from '../realtime/socket'; import { formatMoney } from '../shared/format'; /** * Confronta i prezzi locali col valore base della merce: segnala dove * conviene comprare (buy sotto il base) e dove conviene vendere (sell sopra). */ function TrendTag({ entry }: { entry: MarketEntry }) { const buyDelta = Math.round((entry.buyPrice / entry.basePrice - 1) * 100); const sellDelta = Math.round((entry.sellPrice / entry.basePrice - 1) * 100); if (sellDelta > 0) { return 📈 vendi +{sellDelta}%; } if (buyDelta < 0) { return 💰 affare {buyDelta}%; } return null; } export function MarketScreen() { const { player, refreshPlayer } = useAuth(); const { toast } = useToast(); const [market, setMarket] = useState(null); const [inventory, setInventory] = useState(null); const [selected, setSelected] = useState(null); const [quantity, setQuantity] = useState(1); const [busy, setBusy] = useState(false); const reload = useCallback(async () => { const [m, inv] = await Promise.all([api.market(), api.inventory()]); setMarket(m); setInventory(inv); }, []); useEffect(() => { void reload(); }, [reload]); // Il mercato si aggiorna a ogni tick del server useEffect(() => { const socket = getSocket(); if (!socket) return; const onUpdate = () => void reload(); socket.on('market:updated', onUpdate); return () => { socket.off('market:updated', onUpdate); }; }, [reload]); const owned = (itemId: string) => inventory?.items.find((i) => i.itemId === itemId)?.quantity ?? 0; async function trade(kind: 'buy' | 'sell') { if (!selected) return; setBusy(true); try { const fn = kind === 'buy' ? api.buy : api.sell; const res = await fn(selected.itemId, quantity); toast( kind === 'buy' ? `Comprati ${res.quantity}× ${res.itemName} per ${formatMoney(res.total)}` : `Venduti ${res.quantity}× ${res.itemName} per ${formatMoney(res.total)}`, 'success', ); setSelected(null); setQuantity(1); await Promise.all([reload(), refreshPlayer()]); } catch (err) { toast(err instanceof ApiError ? err.message : 'Errore imprevisto', 'error'); } finally { setBusy(false); } } if (!market) return Caricamento mercato...; return ( Mercato di {market.city.name} {market.activeEvents.length > 0 && ( {market.activeEvents.map((e) => ( ⚡ {e.title} ))} )} {market.prices.map((entry) => ( { setSelected(entry); setQuantity(1); }} > {entry.name} {owned(entry.itemId) > 0 && ( ×{owned(entry.itemId)} )} {formatMoney(entry.buyPrice)} {formatMoney(entry.sellPrice)} ))} Prezzo a sinistra: acquisto · a destra: vendita. I prezzi cambiano ogni minuto: compra dove c'è l'affare, vendi dove rende. {selected && ( setSelected(null)}> e.stopPropagation()}> {selected.name} Possiedi: {owned(selected.itemId)} · peso {selected.weight}/unità Quantità setQuantity((q) => Math.max(1, q - 1))}> − setQuantity(Math.max(1, Math.min(100, Number(e.target.value) || 1))) } /> setQuantity((q) => Math.min(100, q + 1))}> + void trade('buy')} > Compra · {formatMoney(selected.buyPrice * quantity)} void trade('sell')} > Vendi · {formatMoney(selected.sellPrice * quantity)} )} ); }
Caricamento mercato...
Prezzo a sinistra: acquisto · a destra: vendita. I prezzi cambiano ogni minuto: compra dove c'è l'affare, vendi dove rende.
Possiedi: {owned(selected.itemId)} · peso {selected.weight}/unitÃ