| 1 | import React, { useEffect, useState } from 'react'; |
| 2 | |
| 3 | interface TimerDisplayProps { |
| 4 | gameTreeId: number | null; |
| 5 | sessionId: number | null; |
| 6 | onTimerExpire?: () => void; |
| 7 | } |
| 8 | |
| 9 | const TimerDisplay: React.FC<TimerDisplayProps> = ({ |
| 10 | gameTreeId, |
| 11 | sessionId, |
| 12 | onTimerExpire |
| 13 | }) => { |
| 14 | const [timerData, setTimerData] = useState({ |
| 15 | remaining: 60, |
| 16 | warningLevel: null as string | null, |
| 17 | expired: false, |
| 18 | paused: false |
| 19 | }); |
| 20 | |
| 21 | useEffect(() => { |
| 22 | if (!gameTreeId) return; |
| 23 | |
| 24 | const checkTimer = async () => { |
| 25 | try { |
| 26 | const response = await fetch( |
| 27 | `${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api'}/trees/filesystem-trees/${gameTreeId}/timer_status/` |
| 28 | ); |
| 29 | |
| 30 | if (response.ok) { |
| 31 | const data = await response.json(); |
| 32 | setTimerData({ |
| 33 | remaining: data.remaining, |
| 34 | warningLevel: data.warning_level, |
| 35 | expired: data.expired, |
| 36 | paused: data.paused |
| 37 | }); |
| 38 | |
| 39 | // Notify parent if timer expired |
| 40 | if (data.expired && onTimerExpire) { |
| 41 | onTimerExpire(); |
| 42 | } |
| 43 | } |
| 44 | } catch (error) { |
| 45 | console.error('Failed to check timer:', error); |
| 46 | } |
| 47 | }; |
| 48 | |
| 49 | // Check immediately |
| 50 | checkTimer(); |
| 51 | |
| 52 | // Then check every second |
| 53 | const interval = setInterval(checkTimer, 1000); |
| 54 | |
| 55 | return () => clearInterval(interval); |
| 56 | }, [gameTreeId, sessionId, onTimerExpire]); |
| 57 | |
| 58 | // Determine display based on warning level |
| 59 | let borderColor = 'border-green-500'; |
| 60 | let textColor = 'text-green-400'; |
| 61 | let statusMessage = null; |
| 62 | let pulseAnimation = ''; |
| 63 | |
| 64 | if (timerData.expired || timerData.remaining <= 0) { |
| 65 | borderColor = 'border-red-600'; |
| 66 | textColor = 'text-red-600'; |
| 67 | statusMessage = 'Escaped!'; |
| 68 | } else if (timerData.warningLevel === 'critical') { |
| 69 | borderColor = 'border-red-500'; |
| 70 | textColor = 'text-red-400'; |
| 71 | statusMessage = 'Escaping!'; |
| 72 | pulseAnimation = 'animate-pulse'; |
| 73 | } else if (timerData.warningLevel === 'alert') { |
| 74 | borderColor = 'border-orange-500'; |
| 75 | textColor = 'text-orange-400'; |
| 76 | statusMessage = 'Burrowing!'; |
| 77 | } else if (timerData.warningLevel === 'warning') { |
| 78 | borderColor = 'border-yellow-500'; |
| 79 | textColor = 'text-yellow-400'; |
| 80 | statusMessage = 'Alert!'; |
| 81 | } |
| 82 | |
| 83 | return ( |
| 84 | <div className={`bg-black/80 backdrop-blur-sm border ${borderColor} rounded-lg p-3 shadow-2xl ${pulseAnimation}`}> |
| 85 | <div className={`${textColor} font-terminal text-sm`}> |
| 86 | <div className="flex items-center justify-between"> |
| 87 | <span>Time:</span> |
| 88 | <span className="font-bold">{Math.max(0, timerData.remaining)}s</span> |
| 89 | </div> |
| 90 | {statusMessage && ( |
| 91 | <div className="text-center text-xs mt-1">{statusMessage}</div> |
| 92 | )} |
| 93 | </div> |
| 94 | </div> |
| 95 | ); |
| 96 | }; |
| 97 | |
| 98 | export default TimerDisplay; |