diff --git a/client/src/components/Countdown.tsx b/client/src/components/Countdown.tsx new file mode 100644 index 0000000..fdd4663 --- /dev/null +++ b/client/src/components/Countdown.tsx @@ -0,0 +1,33 @@ +import { useEffect, useState } from 'react'; + +function remainingSeconds(target: string): number { + return Math.max(0, Math.ceil((new Date(target).getTime() - Date.now()) / 1000)); +} + +/** Conto alla rovescia verso `target`; chiama onDone una volta arrivato a zero. */ +export function Countdown({ target, onDone }: { target: string; onDone?: () => void }) { + const [seconds, setSeconds] = useState(() => remainingSeconds(target)); + + useEffect(() => { + setSeconds(remainingSeconds(target)); + const interval = setInterval(() => { + const left = remainingSeconds(target); + setSeconds(left); + if (left === 0) { + clearInterval(interval); + onDone?.(); + } + }, 1000); + return () => clearInterval(interval); + }, [target]); + + if (seconds === 0) return pronta; + + const mm = Math.floor(seconds / 60); + const ss = String(seconds % 60).padStart(2, '0'); + return ( + + {mm}:{ss} + + ); +} diff --git a/client/src/components/Layout.tsx b/client/src/components/Layout.tsx new file mode 100644 index 0000000..ba8999f --- /dev/null +++ b/client/src/components/Layout.tsx @@ -0,0 +1,76 @@ +import { useEffect } from 'react'; +import { NavLink, Outlet } from 'react-router-dom'; +import { useAuth } from '../auth/AuthContext'; +import { getSocket } from '../realtime/socket'; +import { formatMoney } from '../shared/format'; +import { useToast } from './Toast'; + +const NAV_ITEMS = [ + { to: '/', label: 'Covo', icon: '๐ ' }, + { to: '/market', label: 'Mercato', icon: 'โ๏ธ' }, + { to: '/missions', label: 'Missioni', icon: '๐ฏ' }, + { to: '/inventory', label: 'Zaino', icon: '๐' }, + { to: '/travel', label: 'Cittร ', icon: '๐บ๏ธ' }, +]; + +export function Layout() { + const { player } = useAuth(); + const { toast } = useToast(); + + // Notifiche realtime globali dal server + useEffect(() => { + const socket = getSocket(); + if (!socket) return; + + const onMissionCompleted = (payload: { title?: string }) => { + toast(`Missione "${payload.title ?? ''}" pronta da riscuotere`, 'success'); + }; + const onEventStarted = (payload: { title?: string }) => { + toast(`Nuovo evento: ${payload.title ?? 'evento mondo'}`, 'info'); + }; + const onEventEnded = (payload: { title?: string }) => { + toast(`Evento terminato: ${payload.title ?? ''}`, 'info'); + }; + + socket.on('mission:completed', onMissionCompleted); + socket.on('worldEvent:started', onEventStarted); + socket.on('worldEvent:ended', onEventEnded); + return () => { + socket.off('mission:completed', onMissionCompleted); + socket.off('worldEvent:started', onEventStarted); + socket.off('worldEvent:ended', onEventEnded); + }; + }, [toast]); + + return ( +