Files
gioco-mobile/client/src/screens/MarketScreen.tsx
T
davide 0f91ace43e client: screens and app routing
Add all game screens: Login, Dashboard, Market (buy/sell), Inventory,
Missions, Travel (city selection), Leaderboard; wired via react-router
in App.tsx with protected routes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 23:42:35 +02:00

157 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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';
export function MarketScreen() {
const { player, refreshPlayer } = useAuth();
const { toast } = useToast();
const [market, setMarket] = useState<MarketResponse | null>(null);
const [inventory, setInventory] = useState<InventoryResponse | null>(null);
const [selected, setSelected] = useState<MarketEntry | null>(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 <p className="muted screen">Caricamento mercato...</p>;
return (
<div className="screen">
<h2 className="screen__title">Mercato di {market.city.name}</h2>
{market.activeEvents.length > 0 && (
<div className="banner">
{market.activeEvents.map((e) => (
<span key={e.id}> {e.title}</span>
))}
</div>
)}
<ul className="list">
{market.prices.map((entry) => (
<li
key={entry.itemId}
className="list__row list__row--clickable"
onClick={() => {
setSelected(entry);
setQuantity(1);
}}
>
<div>
<strong>{entry.name}</strong>
{owned(entry.itemId) > 0 && (
<span className="badge badge--small">×{owned(entry.itemId)}</span>
)}
</div>
<div className="prices">
<span className="price price--buy">{formatMoney(entry.buyPrice)}</span>
<span className="price price--sell">{formatMoney(entry.sellPrice)}</span>
</div>
</li>
))}
</ul>
<p className="muted small">
Prezzo a sinistra: acquisto · a destra: vendita. I prezzi cambiano ogni minuto.
</p>
{selected && (
<div className="modal-backdrop" onClick={() => setSelected(null)}>
<div className="modal" onClick={(e) => e.stopPropagation()}>
<h3>{selected.name}</h3>
<p className="muted small">
Possiedi: {owned(selected.itemId)} · peso {selected.weight}/unità
</p>
<label className="quantity">
Quantità
<div className="quantity__controls">
<button type="button" onClick={() => setQuantity((q) => Math.max(1, q - 1))}>
</button>
<input
type="number"
min={1}
max={100}
value={quantity}
onChange={(e) =>
setQuantity(Math.max(1, Math.min(100, Number(e.target.value) || 1)))
}
/>
<button type="button" onClick={() => setQuantity((q) => Math.min(100, q + 1))}>
+
</button>
</div>
</label>
<div className="modal__actions">
<button
type="button"
className="btn btn--primary"
disabled={busy || (player?.money ?? 0) < selected.buyPrice * quantity}
onClick={() => void trade('buy')}
>
Compra · {formatMoney(selected.buyPrice * quantity)}
</button>
<button
type="button"
className="btn btn--secondary"
disabled={busy || owned(selected.itemId) < quantity}
onClick={() => void trade('sell')}
>
Vendi · {formatMoney(selected.sellPrice * quantity)}
</button>
</div>
</div>
</div>
)}
</div>
);
}