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