TypeScript · 7867 bytes Raw Blame History
1 import React, { useState, useEffect } from 'react';
2 import { GameStats, LeaderboardEntry, gameApi } from '@/lib/api';
3
4 interface GameOverModalProps {
5 isOpen: boolean;
6 gameStats: GameStats | null;
7 sessionId: number | null;
8 onClose: () => void;
9 onNewGame: () => void;
10 }
11
12 const GameOverModal: React.FC<GameOverModalProps> = ({
13 isOpen,
14 gameStats,
15 sessionId,
16 onNewGame,
17 }) => {
18 const [playerName, setPlayerName] = useState('');
19 const [submitted, setSubmitted] = useState(false);
20 const [leaderboard, setLeaderboard] = useState<LeaderboardEntry[]>([]);
21 const [leaderboardPosition, setLeaderboardPosition] = useState<number | null>(null);
22 const [showLeaderboard, setShowLeaderboard] = useState(false);
23 const [loading, setLoading] = useState(false);
24
25 useEffect(() => {
26 if (isOpen && !submitted) {
27 // Load leaderboard when modal opens
28 loadLeaderboard();
29 }
30 }, [isOpen, submitted]);
31
32 const loadLeaderboard = async () => {
33 try {
34 const response = await gameApi.getLeaderboard();
35 setLeaderboard(response.leaderboard);
36 } catch (error) {
37 console.error('Failed to load leaderboard:', error);
38 }
39 };
40
41 const handleSubmit = async (e: React.FormEvent | React.MouseEvent) => {
42 e.preventDefault();
43 if (!sessionId || submitted) return;
44
45 setLoading(true);
46 try {
47 const response = await gameApi.savePlayerName(
48 sessionId,
49 playerName.trim() || 'Anonymous'
50 );
51 setLeaderboardPosition(response.leaderboard_position);
52 setSubmitted(true);
53 await loadLeaderboard();
54 setShowLeaderboard(true);
55 } catch (error) {
56 console.error('Failed to save score:', error);
57 } finally {
58 setLoading(false);
59 }
60 };
61
62 if (!isOpen || !gameStats) return null;
63
64 return (
65 <div className="fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center z-50">
66 <div className="bg-gray-900 border-2 border-green-500 rounded-lg p-8 max-w-2xl w-full mx-4 shadow-2xl">
67 {/* Header */}
68 <h2 className="text-3xl font-bold text-green-400 mb-6 text-center font-terminal">
69 GAME OVER
70 </h2>
71
72 {/* Score Display */}
73 {!showLeaderboard && (
74 <>
75 <div className="bg-black/50 border border-green-500/50 rounded-lg p-6 mb-6">
76 <div className="grid grid-cols-2 gap-4 text-green-300 font-terminal">
77 <div>
78 <span className="text-gray-500">Final Score:</span>
79 <div className="text-2xl font-bold text-green-400">{gameStats.score}</div>
80 </div>
81 <div>
82 <span className="text-gray-500">Moles Killed:</span>
83 <div className="text-2xl font-bold">{gameStats.moles_killed}</div>
84 </div>
85 <div>
86 <span className="text-gray-500">Moles Escaped:</span>
87 <div className="text-xl text-red-400">{gameStats.moles_escaped}</div>
88 </div>
89 <div>
90 <span className="text-gray-500">Commands Used:</span>
91 <div className="text-xl">{gameStats.commands_used}</div>
92 </div>
93 <div className="col-span-2">
94 <span className="text-gray-500">Time Played:</span>
95 <div className="text-xl">{gameStats.time_taken}</div>
96 </div>
97 </div>
98 </div>
99
100 {/* Name Input */}
101 {!submitted ? (
102 <div className="mb-6">
103 <label className="block text-green-400 mb-2 font-terminal">
104 Enter your name for the leaderboard:
105 </label>
106 <div className="flex gap-2">
107 <input
108 type="text"
109 value={playerName}
110 onChange={(e) => setPlayerName(e.target.value)}
111 onKeyDown={(e) => {
112 if (e.key === 'Enter') {
113 e.preventDefault();
114 handleSubmit(e);
115 }
116 }}
117 placeholder="Anonymous"
118 maxLength={20}
119 className="flex-1 bg-black border border-green-500 text-green-300 px-4 py-2 rounded font-terminal focus:outline-none focus:border-green-400"
120 autoFocus
121 disabled={loading}
122 />
123 <button
124 onClick={handleSubmit}
125 disabled={loading}
126 className="px-6 py-2 bg-green-600 hover:bg-green-700 text-white rounded font-terminal transition disabled:opacity-50"
127 >
128 {loading ? 'Saving...' : 'Submit'}
129 </button>
130 </div>
131 </div>
132 ) : (
133 <div className="text-center mb-6">
134 <p className="text-green-400 font-terminal text-lg">
135 Score saved! You ranked #{leaderboardPosition}
136 </p>
137 <button
138 onClick={() => setShowLeaderboard(true)}
139 className="mt-2 text-green-300 hover:text-green-200 underline font-terminal"
140 >
141 View Leaderboard
142 </button>
143 </div>
144 )}
145 </>
146 )}
147
148 {/* Leaderboard */}
149 {showLeaderboard && (
150 <div className="bg-black/50 border border-green-500/50 rounded-lg p-4 mb-6 max-h-96 overflow-y-auto">
151 <h3 className="text-xl font-bold text-green-400 mb-4 font-terminal">Top Scores</h3>
152 <div className="space-y-2">
153 {leaderboard.map((entry) => (
154 <div
155 key={`${entry.player_name}-${entry.completed_at}`}
156 className={`flex justify-between items-center p-2 rounded ${
157 entry.score === gameStats.score && submitted
158 ? 'bg-green-900/50 border border-green-500'
159 : 'hover:bg-gray-800/50'
160 }`}
161 >
162 <div className="flex items-center gap-4 font-terminal">
163 <span className="text-green-500 font-bold w-8">#{entry.rank}</span>
164 <span className="text-green-300">{entry.player_name}</span>
165 </div>
166 <div className="flex items-center gap-6 text-sm font-terminal">
167 <span className="text-green-400">{entry.score} pts</span>
168 <span className="text-gray-500">{entry.moles_killed} moles</span>
169 <span className="text-gray-600">{entry.commands_used} cmds</span>
170 </div>
171 </div>
172 ))}
173 </div>
174 </div>
175 )}
176
177 {/* Action Buttons */}
178 <div className="flex justify-center gap-4">
179 <button
180 onClick={onNewGame}
181 className="px-6 py-3 bg-green-600 hover:bg-green-700 text-white rounded-lg font-terminal transition transform hover:scale-105"
182 >
183 New Game
184 </button>
185 {!showLeaderboard && submitted && (
186 <button
187 onClick={() => setShowLeaderboard(true)}
188 className="px-6 py-3 bg-gray-700 hover:bg-gray-600 text-white rounded-lg font-terminal transition"
189 >
190 View Leaderboard
191 </button>
192 )}
193 {showLeaderboard && (
194 <button
195 onClick={() => setShowLeaderboard(false)}
196 className="px-6 py-3 bg-gray-700 hover:bg-gray-600 text-white rounded-lg font-terminal transition"
197 >
198 Back to Score
199 </button>
200 )}
201 </div>
202 </div>
203 </div>
204 );
205 };
206
207 export default GameOverModal;