1- import { Activity , LayoutGrid , LogOut , Mic , Moon , Settings , Users } from 'lucide-react' ;
1+ import {
2+ Activity ,
3+ LayoutGrid ,
4+ LogOut ,
5+ Mic ,
6+ Moon ,
7+ Settings ,
8+ Settings2 ,
9+ Sun ,
10+ Users ,
11+ } from 'lucide-react' ;
212import { useTranslation } from '@/lib/i18n' ;
313import { ThemeToggle } from '@/components/ui/ThemeToggle' ;
414import { useAuth } from '@/features/auth/contexts/AuthContext' ;
5- import { useLocation } from 'react-router-dom' ;
15+ import { useLocation , useNavigate } from 'react-router-dom' ;
616import { DiscordAvatar , DiscordName } from '@/components/DiscordUser' ;
17+ import { Player } from '@/api/types.gen' ;
18+ import { GAME_STEPS } from '@/features/game/constants' ;
719
820interface SidebarProps {
921 onLogout : ( ) => void ;
@@ -16,6 +28,16 @@ interface SidebarProps {
1628 onToggleSpectatorMode : ( ) => void ;
1729 isSpectatorMode : boolean ;
1830 isConnected : boolean ;
31+
32+ // Game Header Relocated Props
33+ dayCount : number ;
34+ timerSeconds : number ;
35+ speech ?: any ;
36+ players ?: Player [ ] ;
37+ currentStep ?: string ;
38+ currentState ?: string ;
39+ guildId ?: string ;
40+ isManualStep ?: boolean ;
1941}
2042
2143export const Sidebar : React . FC < SidebarProps > = ( {
@@ -29,10 +51,19 @@ export const Sidebar: React.FC<SidebarProps> = ({
2951 onToggleSpectatorMode,
3052 isSpectatorMode,
3153 isConnected,
54+ dayCount,
55+ timerSeconds,
56+ speech,
57+ players,
58+ currentStep,
59+ currentState,
60+ guildId,
61+ isManualStep = false ,
3262} ) => {
3363 const { t } = useTranslation ( ) ;
3464 const { user } = useAuth ( ) ;
3565 const location = useLocation ( ) ;
66+ const navigate = useNavigate ( ) ;
3667
3768 const isDashboardActive =
3869 location . pathname . endsWith ( user ?. user ?. guildId ? `/server/${ user . user . guildId } ` : '/' ) &&
@@ -45,6 +76,15 @@ export const Sidebar: React.FC<SidebarProps> = ({
4576 const isSpectatorActive = location . pathname . includes ( '/spectator' ) ;
4677 const isSpeechActive = location . pathname . includes ( '/speech' ) ;
4778
79+ const currentSpeaker =
80+ speech ?. currentSpeakerId && players
81+ ? players . find ( ( p ) => p . id === speech . currentSpeakerId )
82+ : null ;
83+
84+ const displayDay = dayCount === 0 && currentState !== 'SETUP' && ! ! currentState ? 1 : dayCount ;
85+ const isNight = currentState ?. includes ( 'NIGHT' ) ;
86+ const isLobby = currentState === 'SETUP' || ! currentState ;
87+
4888 return (
4989 < aside className = "w-full md:w-64 bg-slate-100 dark:bg-slate-950 border-r border-slate-300 dark:border-slate-800 flex flex-col shrink-0" >
5090 < div className = "p-6 flex items-center gap-3 border-b border-slate-300 dark:border-slate-800" >
@@ -57,7 +97,42 @@ export const Sidebar: React.FC<SidebarProps> = ({
5797 </ span >
5898 </ div >
5999
60- < nav className = "flex-1 p-4 space-y-1" >
100+ < div className = "p-4 border-b border-slate-300 dark:border-slate-800 space-y-4" >
101+ { /* Game Status */ }
102+ < div className = "bg-white dark:bg-slate-900 rounded-xl p-3 border border-slate-200 dark:border-slate-800 shadow-sm" >
103+ < div className = "flex items-center gap-2 mb-2 text-slate-900 dark:text-slate-100" >
104+ { isLobby ? (
105+ < Settings2 className = "w-4 h-4 text-slate-500 dark:text-slate-400" />
106+ ) : isNight ? (
107+ < Moon className = "w-4 h-4 text-indigo-500 dark:text-indigo-400" />
108+ ) : (
109+ < Sun className = "w-4 h-4 text-orange-500" />
110+ ) }
111+ < span className = "font-bold text-sm" >
112+ { ! isLobby && displayDay > 0 && t ( 'game.day' , { day : String ( displayDay ) } ) + ' - ' }
113+ { ( ( ) => {
114+ const stepInfo = GAME_STEPS . find ( ( s ) => s . id === currentStep ) ;
115+ return stepInfo ? t ( stepInfo . key ) : currentStep || 'LOBBY' ;
116+ } ) ( ) }
117+ </ span >
118+ </ div >
119+
120+ { ! isManualStep && ! isLobby && (
121+ < div className = "flex items-center justify-between" >
122+ < span className = "text-[10px] text-slate-500 dark:text-slate-400 font-bold uppercase tracking-wider" >
123+ { t ( 'game.timer' ) }
124+ </ span >
125+ < div
126+ className = { `font-mono font-bold text-lg ${ timerSeconds < 10 ? 'text-red-500 dark:text-red-400' : 'text-slate-800 dark:text-slate-200' } ` }
127+ >
128+ { Math . floor ( timerSeconds / 60 ) } :{ String ( timerSeconds % 60 ) . padStart ( 2 , '0' ) }
129+ </ div >
130+ </ div >
131+ ) }
132+ </ div >
133+ </ div >
134+
135+ < nav className = "flex-1 p-4 space-y-1 overflow-y-auto overflow-x-hidden" >
61136 { user ?. user ?. role === 'JUDGE' && ! isSpectatorMode && (
62137 < >
63138 < button
@@ -127,39 +202,70 @@ export const Sidebar: React.FC<SidebarProps> = ({
127202 < div className = "p-4 border-t border-slate-300 dark:border-slate-800 space-y-3" >
128203 { /* User Profile */ }
129204 { user && (
130- < div className = "flex items-center gap-3 px-2 py-2 bg-slate-200/50 dark:bg-slate-800/50 rounded-lg" >
131- < DiscordAvatar
132- userId = { user . user . userId }
133- guildId = { user . user . guildId }
134- avatarClassName = "w-10 h-10 rounded-full ring-2 ring-indigo-200 dark:ring-indigo-900"
135- />
136- < div className = "flex-1 min-w-0" >
137- < p className = "text-sm font-medium text-slate-900 dark:text-white truncate" >
138- < DiscordName userId = { user . user . userId } guildId = { user . user . guildId } />
139- </ p >
140- { user . user . role === 'JUDGE' ? (
141- < button
142- onClick = { onToggleSpectatorMode }
143- className = { `inline-block px-2 py-0.5 text-xs font-medium rounded transition-colors cursor-pointer ${
144- isSpectatorMode
145- ? 'bg-blue-100 dark:bg-blue-950 text-blue-700 dark:text-blue-300 hover:bg-blue-200 dark:hover:bg-blue-900'
146- : 'bg-purple-100 dark:bg-purple-950 text-purple-700 dark:text-purple-300 hover:bg-purple-200 dark:hover:bg-purple-900'
147- } `}
148- title = { isSpectatorMode ? t ( 'sidebar.backToJudge' ) : t ( 'sidebar.viewAsSpectator' ) }
149- >
150- { isSpectatorMode ? t ( 'userRoles.SPECTATOR' ) : t ( 'userRoles.JUDGE' ) }
151- </ button >
152- ) : (
153- < span
154- className = { `inline-block px-2 py-0.5 text-xs font-medium rounded ${
155- user . user . role === 'SPECTATOR'
156- ? 'bg-blue-100 dark:bg-blue-950 text-blue-700 dark:text-blue-300'
157- : 'bg-gray-100 dark:bg-gray-900 text-gray-700 dark:text-gray-300'
158- } `}
159- >
160- { t ( `userRoles.${ user . user . role } ` ) || user . user . role }
205+ < div className = "space-y-3" >
206+ { currentSpeaker && (
207+ < button
208+ onClick = { ( ) => navigate ( `/server/${ guildId } /speech` , { replace : true } ) }
209+ className = "w-full flex flex-col items-center gap-1 p-3 bg-indigo-50 dark:bg-indigo-950/30 border border-indigo-200 dark:border-indigo-800/50 rounded-xl transition-all hover:bg-indigo-100 dark:hover:bg-indigo-950/50 group"
210+ >
211+ < div className = "flex items-center gap-2 text-indigo-600 dark:text-indigo-400" >
212+ < Mic className = "w-3 h-3 animate-pulse" />
213+ < span className = "text-[10px] font-bold uppercase tracking-wider" >
214+ { t ( 'messages.speaking' ) }
215+ </ span >
216+ </ div >
217+ < span className = "font-bold text-sm text-slate-900 dark:text-slate-100" >
218+ { currentSpeaker . nickname }
161219 </ span >
162- ) }
220+ { speech ?. endTime && (
221+ < span className = "text-xs font-mono text-indigo-600 dark:text-indigo-400 bg-indigo-100/50 dark:bg-indigo-900/30 px-2 py-0.5 rounded-full border border-indigo-200/50 dark:border-indigo-800/30 mt-1" >
222+ { ( ( ) => {
223+ const seconds = Math . max ( 0 , Math . ceil ( ( speech . endTime - Date . now ( ) ) / 1000 ) ) ;
224+ return `${ Math . floor ( seconds / 60 )
225+ . toString ( )
226+ . padStart ( 2 , '0' ) } :${ String ( seconds % 60 ) . padStart ( 2 , '0' ) } `;
227+ } ) ( ) }
228+ </ span >
229+ ) }
230+ </ button >
231+ ) }
232+
233+ < div className = "flex items-center gap-3 px-2 py-2 bg-slate-200/50 dark:bg-slate-800/50 rounded-lg" >
234+ < DiscordAvatar
235+ userId = { user . user . userId }
236+ guildId = { user . user . guildId }
237+ avatarClassName = "w-10 h-10 rounded-full ring-2 ring-indigo-200 dark:ring-indigo-900"
238+ />
239+ < div className = "flex-1 min-w-0" >
240+ < p className = "text-sm font-medium text-slate-900 dark:text-white truncate" >
241+ < DiscordName userId = { user . user . userId } guildId = { user . user . guildId } />
242+ </ p >
243+ { user . user . role === 'JUDGE' ? (
244+ < button
245+ onClick = { onToggleSpectatorMode }
246+ className = { `inline-block px-2 py-0.5 text-xs font-medium rounded transition-colors cursor-pointer ${
247+ isSpectatorMode
248+ ? 'bg-blue-100 dark:bg-blue-950 text-blue-700 dark:text-blue-300 hover:bg-blue-200 dark:hover:bg-blue-900'
249+ : 'bg-purple-100 dark:bg-purple-950 text-purple-700 dark:text-purple-300 hover:bg-purple-200 dark:hover:bg-purple-900'
250+ } `}
251+ title = {
252+ isSpectatorMode ? t ( 'sidebar.backToJudge' ) : t ( 'sidebar.viewAsSpectator' )
253+ }
254+ >
255+ { isSpectatorMode ? t ( 'userRoles.SPECTATOR' ) : t ( 'userRoles.JUDGE' ) }
256+ </ button >
257+ ) : (
258+ < span
259+ className = { `inline-block px-2 py-0.5 text-xs font-medium rounded ${
260+ user . user . role === 'SPECTATOR'
261+ ? 'bg-blue-100 dark:bg-blue-950 text-blue-700 dark:text-blue-300'
262+ : 'bg-gray-100 dark:bg-gray-900 text-gray-700 dark:text-gray-300'
263+ } `}
264+ >
265+ { t ( `userRoles.${ user . user . role } ` ) || user . user . role }
266+ </ span >
267+ ) }
268+ </ div >
163269 </ div >
164270 </ div >
165271 ) }
0 commit comments