feat: Implementar sesiones persistentes y botones de salida
- Añadido sistema de sesiones con localStorage - Nuevo hook useSessionStorage para manejar sesiones - Botón de salir de la partida (ExitGameButton) en todas las pantallas del juego - Botón de logout (LogoutButton) solo en el lobby - Evento leave_game en servidor para cerrar partida cuando alguien sale - Evento reconnect_session para reconectar jugadores después de recargar - Actualizado GameBoard para incluir botón de salida - Actualizado page.tsx para manejar sesiones y logout
This commit is contained in:
80
client/src/components/ExitGameButton.tsx
Normal file
80
client/src/components/ExitGameButton.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { useState } from 'react';
|
||||
|
||||
interface ExitGameButtonProps {
|
||||
onExit: () => void;
|
||||
playerName: string;
|
||||
}
|
||||
|
||||
export default function ExitGameButton({ onExit, playerName }: ExitGameButtonProps) {
|
||||
const [showConfirm, setShowConfirm] = useState(false);
|
||||
|
||||
const handleConfirmExit = () => {
|
||||
setShowConfirm(false);
|
||||
onExit();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<motion.button
|
||||
onClick={() => setShowConfirm(true)}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="fixed top-4 left-4 z-50 bg-yellow-900/80 hover:bg-yellow-800 text-white p-3 rounded-lg border border-yellow-700/50 backdrop-blur-sm shadow-lg transition-all group"
|
||||
title="Abandonar partida"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-5 w-5"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-xs font-bold uppercase tracking-wider hidden md:inline">Salir</span>
|
||||
</div>
|
||||
</motion.button>
|
||||
|
||||
{/* Modal de confirmación */}
|
||||
{showConfirm && (
|
||||
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/80 backdrop-blur-sm">
|
||||
<motion.div
|
||||
initial={{ scale: 0.9, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
className="bg-zinc-900 p-6 rounded-lg border border-red-700/50 w-full max-w-md mx-4 shadow-2xl"
|
||||
>
|
||||
<h3 className="text-xl font-bold text-red-400 mb-4 uppercase flex items-center gap-2">
|
||||
⚠️ Abandonar Partida
|
||||
</h3>
|
||||
<p className="text-gray-300 mb-2">
|
||||
¿Estás seguro de que quieres abandonar la partida?
|
||||
</p>
|
||||
<p className="text-sm text-gray-400 mb-6">
|
||||
La partida se cerrará para todos los jugadores y se perderá todo el progreso.
|
||||
</p>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
onClick={() => setShowConfirm(false)}
|
||||
className="flex-1 py-3 bg-gray-700 hover:bg-gray-600 text-white rounded font-bold uppercase text-sm transition-colors"
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
<button
|
||||
onClick={handleConfirmExit}
|
||||
className="flex-1 py-3 bg-red-900 hover:bg-red-800 text-white rounded font-bold uppercase text-sm transition-colors"
|
||||
>
|
||||
Salir
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -5,14 +5,16 @@ import { GameState, GamePhase, Player, GAME_CONFIG, Faction } from '../../../sha
|
||||
import MissionReveal from './MissionReveal';
|
||||
import MissionResult from './MissionResult';
|
||||
import VictoryScreen from './VictoryScreen';
|
||||
import ExitGameButton from './ExitGameButton';
|
||||
|
||||
interface GameBoardProps {
|
||||
gameState: GameState;
|
||||
currentPlayerId: string;
|
||||
actions: any;
|
||||
fullPlayerName: string;
|
||||
}
|
||||
|
||||
export default function GameBoard({ gameState, currentPlayerId, actions }: GameBoardProps) {
|
||||
export default function GameBoard({ gameState, currentPlayerId, actions, fullPlayerName }: GameBoardProps) {
|
||||
const [selectedTeam, setSelectedTeam] = useState<string[]>([]);
|
||||
|
||||
// Hooks para FASE REVEAL ROLE
|
||||
@@ -298,6 +300,14 @@ export default function GameBoard({ gameState, currentPlayerId, actions }: GameB
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-screen flex flex-col overflow-hidden">
|
||||
{/* Botón de Salir de la Partida - No mostrar en pantallas de victoria */}
|
||||
{gameState.phase !== GamePhase.ALLIED_WIN && gameState.phase !== GamePhase.NAZIS_WIN && (
|
||||
<ExitGameButton
|
||||
onExit={() => actions.leaveGame()}
|
||||
playerName={fullPlayerName}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Fondo */}
|
||||
<div className="absolute inset-0 z-0 opacity-40">
|
||||
<Image
|
||||
|
||||
33
client/src/components/LogoutButton.tsx
Normal file
33
client/src/components/LogoutButton.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
interface LogoutButtonProps {
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export default function LogoutButton({ onClick }: LogoutButtonProps) {
|
||||
return (
|
||||
<motion.button
|
||||
onClick={onClick}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="fixed top-4 left-4 z-50 bg-red-900/80 hover:bg-red-800 text-white p-3 rounded-lg border border-red-700/50 backdrop-blur-sm shadow-lg transition-all group"
|
||||
title="Salir del juego"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-5 w-5"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8 7a1 1 0 00-1 1v4a1 1 0 001 1h4a1 1 0 001-1V8a1 1 0 00-1-1H8z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-xs font-bold uppercase tracking-wider hidden md:inline">Salir</span>
|
||||
</div>
|
||||
</motion.button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user