client: market trend tags + leaderboard live updates via Socket.IO

Market rows show a TrendTag: 💰 affare when buyPrice < basePrice,
📈 vendi when sellPrice > basePrice. Leaderboard subscribes to
leaderboard:updated socket event to refresh without polling.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 00:12:14 +02:00
parent 18de7fda21
commit 6abd115c81
2 changed files with 31 additions and 1 deletions
+12
View File
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
import { api } from '../api/api'; import { api } from '../api/api';
import type { LeaderboardResponse } from '../api/types'; import type { LeaderboardResponse } from '../api/types';
import { useAuth } from '../auth/AuthContext'; import { useAuth } from '../auth/AuthContext';
import { getSocket } from '../realtime/socket';
import { formatMoney } from '../shared/format'; import { formatMoney } from '../shared/format';
type Metric = 'money' | 'reputation'; type Metric = 'money' | 'reputation';
@@ -16,6 +17,17 @@ export function LeaderboardScreen() {
void api.leaderboard(metric).then(setBoard); void api.leaderboard(metric).then(setBoard);
}, [metric]); }, [metric]);
// Aggiornamento live quando il server ricalcola le classifiche
useEffect(() => {
const socket = getSocket();
if (!socket) return;
const onUpdate = () => void api.leaderboard(metric).then(setBoard);
socket.on('leaderboard:updated', onUpdate);
return () => {
socket.off('leaderboard:updated', onUpdate);
};
}, [metric]);
return ( return (
<div className="screen"> <div className="screen">
<h2 className="screen__title">Classifiche</h2> <h2 className="screen__title">Classifiche</h2>
+19 -1
View File
@@ -7,6 +7,22 @@ import { useToast } from '../components/Toast';
import { getSocket } from '../realtime/socket'; import { getSocket } from '../realtime/socket';
import { formatMoney } from '../shared/format'; 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 <span className="trend trend--sell">📈 vendi +{sellDelta}%</span>;
}
if (buyDelta < 0) {
return <span className="trend trend--buy">💰 affare {buyDelta}%</span>;
}
return null;
}
export function MarketScreen() { export function MarketScreen() {
const { player, refreshPlayer } = useAuth(); const { player, refreshPlayer } = useAuth();
const { toast } = useToast(); const { toast } = useToast();
@@ -95,12 +111,14 @@ export function MarketScreen() {
<div className="prices"> <div className="prices">
<span className="price price--buy">{formatMoney(entry.buyPrice)}</span> <span className="price price--buy">{formatMoney(entry.buyPrice)}</span>
<span className="price price--sell">{formatMoney(entry.sellPrice)}</span> <span className="price price--sell">{formatMoney(entry.sellPrice)}</span>
<TrendTag entry={entry} />
</div> </div>
</li> </li>
))} ))}
</ul> </ul>
<p className="muted small"> <p className="muted small">
Prezzo a sinistra: acquisto · a destra: vendita. I prezzi cambiano ogni minuto. Prezzo a sinistra: acquisto · a destra: vendita. I prezzi cambiano ogni minuto: compra
dove c'è l'affare, vendi dove rende.
</p> </p>
{selected && ( {selected && (