diff --git a/client/src/screens/DashboardScreen.tsx b/client/src/screens/DashboardScreen.tsx index ddceaaf..4e0069e 100644 --- a/client/src/screens/DashboardScreen.tsx +++ b/client/src/screens/DashboardScreen.tsx @@ -1,27 +1,65 @@ -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import { api } from '../api/api'; -import type { PlayerMeResponse, WorldEvent } from '../api/types'; +import { ApiError } from '../api/http'; +import type { ActiveMission, PlayerMeResponse, WorldEvent } from '../api/types'; import { useAuth } from '../auth/AuthContext'; +import { Countdown } from '../components/Countdown'; +import { useToast } from '../components/Toast'; import { EVENT_TYPE_LABELS, formatMoney, riskLabel } from '../shared/format'; export function DashboardScreen() { const { player, setPlayer, logout } = useAuth(); + const { toast } = useToast(); const [me, setMe] = useState(null); const [events, setEvents] = useState([]); + const [active, setActive] = useState([]); + const [busy, setBusy] = useState(false); + // tick fittizio per ri-renderizzare quando un countdown arriva a zero + const [, setTick] = useState(0); + + const reload = useCallback(async () => { + const [meRes, eventsRes, activeRes] = await Promise.all([ + api.playerMe(), + api.events(), + api.missionsActive(), + ]); + setMe(meRes); + setPlayer(meRes.player); + setEvents(eventsRes.events); + setActive(activeRes.missions); + }, [setPlayer]); useEffect(() => { - void api.playerMe().then((res) => { - setMe(res); + void reload(); + }, [reload]); + + async function quickClaim(playerMissionId: string) { + setBusy(true); + try { + const res = await api.claimMission(playerMissionId); setPlayer(res.player); - }); - void api.events().then((res) => setEvents(res.events)); - }, [setPlayer]); + toast( + res.success + ? `✅ "${res.missionTitle}": +${formatMoney(res.rewardMoney)}, +${res.rewardXp} XP` + + (res.lootItemName ? `, 🎁 ${res.lootItemQuantity}× ${res.lootItemName}` : '') + : `❌ "${res.missionTitle}" fallita` + + (res.fine > 0 ? `: multa −${formatMoney(res.fine)}` : ''), + res.success ? 'success' : 'error', + ); + await reload(); + } catch (err) { + toast(err instanceof ApiError ? err.message : 'Errore imprevisto', 'error'); + } finally { + setBusy(false); + } + } if (!player) return null; const xpNext = me?.xpForNextLevel ?? null; const xpRatio = xpNext ? Math.min(1, player.experience / xpNext) : 0; + const isReady = (m: ActiveMission) => new Date(m.completesAt).getTime() <= Date.now(); return (
@@ -55,6 +93,46 @@ export function DashboardScreen() { )} + {active.length > 0 && ( +
+

Missioni in corso

+
    + {active.map((m) => ( +
  • +
    + {m.mission.title} +

    {formatMoney(m.mission.rewardMoney)}

    +
    + {isReady(m) ? ( + + ) : ( + setTick((t) => t + 1)} /> + )} +
  • + ))} +
+
+ )} + +
+ + 🎯 Missioni + + + ⚖️ Mercato + + + 🏆 Classifiche + +
+

Eventi in corso

{events.length === 0 ? ( @@ -78,9 +156,6 @@ export function DashboardScreen() { )}
- - 🏆 Classifiche -