@@ -90,7 +90,7 @@ const COMPLEXITY_COLOR = { easy: '#28a745', medium: '#fd7e14', hard: '#dc3545' }
9090
9191// ── Settings panel (shared by admin/host) ────────────────────────────────────
9292
93- function SettingsPanel ( { settings, categories, onChange, onApply, title } ) {
93+ function SettingsPanel ( { settings, categories, onChange, onApply, title, players , myUserId , onSetHost } ) {
9494 const [ local , setLocal ] = useState ( settings ) ;
9595 const [ showCategoryModal , setShowCategoryModal ] = useState ( false ) ;
9696 const [ categorySearch , setCategorySearch ] = useState ( '' ) ;
@@ -194,6 +194,23 @@ function SettingsPanel({ settings, categories, onChange, onApply, title }) {
194194
195195 { onChange && < div style = { { borderTop : '1px solid var(--border-color,#ddd)' , paddingTop : '10px' } } > { toggle ( 'Public (show in room list)' , local . isPublic , v => set ( 'isPublic' , v ) ) } </ div > }
196196
197+ { onSetHost && players && players . length > 1 && (
198+ < div style = { { borderTop : '1px solid var(--border-color,#ddd)' , paddingTop : '10px' } } >
199+ < span style = { label } > Transfer Host</ span >
200+ < div style = { { display : 'flex' , gap : '8px' , alignItems : 'center' } } >
201+ < select id = "set-host-select" style = { { flex : 1 , padding : '6px 8px' , borderRadius : '6px' , border : '1px solid var(--border-color,#ddd)' , background : 'var(--card-bg,#fff)' , color : 'var(--text-color,#333)' , fontSize : '0.85rem' } } >
202+ { players . filter ( p => p . userId && p . userId !== myUserId ) . map ( p => (
203+ < option key = { p . userId } value = { p . socketId } > { p . displayName } { p . isHost ? ' (current host)' : '' } </ option >
204+ ) ) }
205+ </ select >
206+ < button style = { btn ( '#6c757d' , { padding : '6px 12px' , fontSize : '0.8rem' } ) } onClick = { ( ) => {
207+ const sel = document . getElementById ( 'set-host-select' ) ;
208+ if ( sel ?. value ) onSetHost ( sel . value ) ;
209+ } } > Transfer</ button >
210+ </ div >
211+ </ div >
212+ ) }
213+
197214 < button style = { btn ( ) } onClick = { ( ) => onApply ( local ) } > Apply Settings</ button >
198215 </ div >
199216 </ div >
@@ -231,11 +248,11 @@ export default function SharePlay() {
231248 // Report / Suggest (persist across question changes intentionally)
232249 const [ showRequestModal , setShowRequestModal ] = useState ( false ) ;
233250 const [ showReportMenu , setShowReportMenu ] = useState ( false ) ;
251+ const [ reportPlayerId , setReportPlayerId ] = useState ( '' ) ;
234252 const [ reportType , setReportType ] = useState ( 'general' ) ;
235253 const [ reportNote , setReportNote ] = useState ( '' ) ;
236254 const [ reportMessage , setReportMessage ] = useState ( '' ) ;
237255 const reportMenuRef = useRef ( null ) ;
238- const reportingQIdRef = useRef ( null ) ; // capture question ID at open time
239256
240257 // Question / voting
241258 const [ currentQuestion , setCurrentQuestion ] = useState ( null ) ;
@@ -488,28 +505,35 @@ export default function SharePlay() {
488505 const handleSuggest = useCallback ( ( ) => setShowRequestModal ( true ) , [ ] ) ;
489506
490507 const handleReport = useCallback ( async ( reasonOverride ) => {
491- const qId = reportingQIdRef . current ;
492- if ( ! qId ) return ;
508+ if ( ! reportPlayerId ) return ;
493509 try {
494510 const token = getToken ( ) ;
495511 const headers = token ? { Authorization : `Bearer ${ token } ` } : undefined ;
496- await axios . post ( `${ REST_BASE } /api/game/report` , { questionId : qId , reason : reasonOverride || 'General Report' } , { headers } ) ;
497- setReportMessage ( '✅ Reported successfully.' ) ;
512+ const reportedPlayer = players . find ( p => p . userId === Number ( reportPlayerId ) ) ;
513+ await axios . post ( `${ REST_BASE } /api/game/report-player` , {
514+ reportedUserId : Number ( reportPlayerId ) ,
515+ roomCode,
516+ reason : reasonOverride || 'General Report' ,
517+ note : reportNote . trim ( ) || undefined ,
518+ } , { headers } ) ;
519+ setReportMessage ( `✅ Reported ${ reportedPlayer ?. displayName || 'player' } successfully.` ) ;
498520 } catch ( err ) {
499- setReportMessage ( `⚠️ ${ err . response ?. data ?. error || 'Question flagged for review .' } ` ) ;
521+ setReportMessage ( `⚠️ ${ err . response ?. data ?. error || 'Failed to report player .' } ` ) ;
500522 }
501- } , [ ] ) ;
523+ } , [ reportPlayerId , players , roomCode , reportNote ] ) ;
502524
503525 const submitReport = useCallback ( async ( ) => {
526+ if ( ! reportPlayerId ) { setReportMessage ( '⚠️ Select a player to report.' ) ; return ; }
504527 const trimmed = reportNote . trim ( ) ;
505- const needsNote = reportType === 'inappropriate' || reportType === 'incorrect ' ;
528+ const needsNote = reportType === 'inappropriate' || reportType === 'cheating ' ;
506529 if ( needsNote && ! trimmed ) { setReportMessage ( '⚠️ Please add a short description.' ) ; return ; }
507- const label = reportType === 'inappropriate' ? 'Inappropriate' : reportType === 'incorrect ' ? 'Incorrect ' : 'General Report' ;
530+ const label = reportType === 'inappropriate' ? 'Inappropriate' : reportType === 'cheating ' ? 'Cheating' : reportType === 'harassment' ? 'Harassment ' : 'General Report' ;
508531 await handleReport ( needsNote ? `${ label } : ${ trimmed } ` : label ) ;
509532 setShowReportMenu ( false ) ;
510533 setReportNote ( '' ) ;
511534 setReportType ( 'general' ) ;
512- } , [ reportNote , reportType , handleReport ] ) ;
535+ setReportPlayerId ( '' ) ;
536+ } , [ reportNote , reportType , reportPlayerId , handleReport ] ) ;
513537
514538 const copyCode = useCallback ( ( ) => {
515539 navigator . clipboard ?. writeText ( roomCode ) . then ( ( ) => { setCopied ( true ) ; setTimeout ( ( ) => setCopied ( false ) , 2000 ) ; } ) . catch ( ( ) => { } ) ;
@@ -1118,6 +1142,9 @@ export default function SharePlay() {
11181142 onChange = { true }
11191143 onApply = { applyHostSettings }
11201144 title = "⚙ Room Settings"
1145+ players = { players }
1146+ myUserId = { user ?. id }
1147+ onSetHost = { ( targetSocketId ) => socketRef . current ?. emit ( 'transfer_host' , { targetSocketId } ) }
11211148 />
11221149 ) }
11231150 </ div >
@@ -1136,24 +1163,32 @@ export default function SharePlay() {
11361163 < button
11371164 style = { { ...btn ( '#6c757d' ) , fontSize : '0.85rem' , padding : '7px 14px' } }
11381165 onClick = { ( ) => {
1139- reportingQIdRef . current = currentQuestion . id ;
11401166 setShowReportMenu ( v => ! v ) ;
11411167 setReportMessage ( '' ) ;
1168+ setReportPlayerId ( '' ) ;
11421169 } }
11431170 >
11441171 ⚠ Report
11451172 </ button >
11461173
11471174 { showReportMenu && (
1148- < div style = { { position : 'absolute' , bottom : '110%' , left : 0 , background : 'var(--card-bg,#fff)' , border : '1px solid var(--border-color,#ddd)' , borderRadius : '10px' , padding : '14px' , width : '260px' , zIndex : 20 , boxShadow : '0 6px 20px rgba(0,0,0,0.15)' } } >
1149- < div style = { { fontSize : '0.8rem' , color : '#888' , marginBottom : '6px' } } > Report Type</ div >
1175+ < div style = { { position : 'absolute' , bottom : '110%' , left : 0 , background : 'var(--card-bg,#fff)' , border : '1px solid var(--border-color,#ddd)' , borderRadius : '10px' , padding : '14px' , width : '280px' , zIndex : 20 , boxShadow : '0 6px 20px rgba(0,0,0,0.15)' } } >
1176+ < div style = { { fontSize : '0.8rem' , color : '#888' , marginBottom : '6px' } } > Report Player</ div >
1177+ < select value = { reportPlayerId } onChange = { e => setReportPlayerId ( e . target . value ) }
1178+ style = { { width : '100%' , padding : '6px 8px' , borderRadius : '6px' , border : '1px solid var(--border-color,#ddd)' , background : 'var(--card-bg,#fff)' , color : 'var(--text-color,#333)' , marginBottom : '8px' } } >
1179+ < option value = "" > Select a player...</ option >
1180+ { players . filter ( p => p . userId && p . userId !== user ?. id ) . map ( p => (
1181+ < option key = { p . userId } value = { p . userId } > { p . displayName } </ option >
1182+ ) ) }
1183+ </ select >
11501184 < select value = { reportType } onChange = { e => setReportType ( e . target . value ) }
11511185 style = { { width : '100%' , padding : '6px 8px' , borderRadius : '6px' , border : '1px solid var(--border-color,#ddd)' , background : 'var(--card-bg,#fff)' , color : 'var(--text-color,#333)' , marginBottom : '8px' } } >
11521186 < option value = "general" > General Report</ option >
1153- < option value = "inappropriate" > Inappropriate</ option >
1154- < option value = "incorrect" > Incorrect Answer</ option >
1187+ < option value = "inappropriate" > Inappropriate Behavior</ option >
1188+ < option value = "cheating" > Cheating</ option >
1189+ < option value = "harassment" > Harassment</ option >
11551190 </ select >
1156- { ( reportType === 'inappropriate' || reportType === 'incorrect ' ) && (
1191+ { ( reportType === 'inappropriate' || reportType === 'cheating' || reportType === 'harassment ') && (
11571192 < textarea value = { reportNote } onChange = { e => setReportNote ( e . target . value ) }
11581193 placeholder = "Add a short description..."
11591194 rows = { 3 }
0 commit comments