@@ -248,11 +248,18 @@ export default function SharePlay() {
248248 // Report / Suggest (persist across question changes intentionally)
249249 const [ showRequestModal , setShowRequestModal ] = useState ( false ) ;
250250 const [ showReportMenu , setShowReportMenu ] = useState ( false ) ;
251- const [ reportPlayerId , setReportPlayerId ] = useState ( '' ) ;
252251 const [ reportType , setReportType ] = useState ( 'general' ) ;
253252 const [ reportNote , setReportNote ] = useState ( '' ) ;
254253 const [ reportMessage , setReportMessage ] = useState ( '' ) ;
255254 const reportMenuRef = useRef ( null ) ;
255+ const reportingQIdRef = useRef ( null ) ;
256+ // Player report
257+ const [ showPlayerReportMenu , setShowPlayerReportMenu ] = useState ( false ) ;
258+ const [ reportPlayerId , setReportPlayerId ] = useState ( '' ) ;
259+ const [ playerReportType , setPlayerReportType ] = useState ( 'general' ) ;
260+ const [ playerReportNote , setPlayerReportNote ] = useState ( '' ) ;
261+ const [ playerReportMsg , setPlayerReportMsg ] = useState ( '' ) ;
262+ const playerReportRef = useRef ( null ) ;
256263
257264 // Question / voting
258265 const [ currentQuestion , setCurrentQuestion ] = useState ( null ) ;
@@ -411,6 +418,13 @@ export default function SharePlay() {
411418 return ( ) => document . removeEventListener ( 'mousedown' , handler ) ;
412419 } , [ showReportMenu ] ) ;
413420
421+ useEffect ( ( ) => {
422+ if ( ! showPlayerReportMenu ) return ;
423+ const handler = e => { if ( playerReportRef . current && ! playerReportRef . current . contains ( e . target ) ) setShowPlayerReportMenu ( false ) ; } ;
424+ document . addEventListener ( 'mousedown' , handler ) ;
425+ return ( ) => document . removeEventListener ( 'mousedown' , handler ) ;
426+ } , [ showPlayerReportMenu ] ) ;
427+
414428 useEffect ( ( ) => {
415429 const handler = e => { if ( e . key === 'Escape' ) setTvMode ( false ) ; } ;
416430 document . addEventListener ( 'keydown' , handler ) ;
@@ -504,7 +518,31 @@ export default function SharePlay() {
504518
505519 const handleSuggest = useCallback ( ( ) => setShowRequestModal ( true ) , [ ] ) ;
506520
507- const handleReport = useCallback ( async ( reasonOverride ) => {
521+ const handleQuestionReport = useCallback ( async ( reasonOverride ) => {
522+ const qId = reportingQIdRef . current ;
523+ if ( ! qId ) return ;
524+ try {
525+ const token = getToken ( ) ;
526+ const headers = token ? { Authorization : `Bearer ${ token } ` } : undefined ;
527+ await axios . post ( `${ REST_BASE } /api/game/report` , { questionId : qId , reason : reasonOverride || 'General Report' } , { headers } ) ;
528+ setReportMessage ( '✅ Reported successfully.' ) ;
529+ } catch ( err ) {
530+ setReportMessage ( `⚠️ ${ err . response ?. data ?. error || 'Question flagged for review.' } ` ) ;
531+ }
532+ } , [ ] ) ;
533+
534+ const submitQuestionReport = useCallback ( async ( ) => {
535+ const trimmed = reportNote . trim ( ) ;
536+ const needsNote = reportType === 'inappropriate' || reportType === 'incorrect' ;
537+ if ( needsNote && ! trimmed ) { setReportMessage ( '⚠️ Please add a short description.' ) ; return ; }
538+ const label = reportType === 'inappropriate' ? 'Inappropriate' : reportType === 'incorrect' ? 'Incorrect Answer' : 'General Report' ;
539+ await handleQuestionReport ( needsNote ? `${ label } : ${ trimmed } ` : label ) ;
540+ setShowReportMenu ( false ) ;
541+ setReportNote ( '' ) ;
542+ setReportType ( 'general' ) ;
543+ } , [ reportNote , reportType , handleQuestionReport ] ) ;
544+
545+ const handlePlayerReport = useCallback ( async ( reasonOverride ) => {
508546 if ( ! reportPlayerId ) return ;
509547 try {
510548 const token = getToken ( ) ;
@@ -514,26 +552,26 @@ export default function SharePlay() {
514552 reportedUserId : Number ( reportPlayerId ) ,
515553 roomCode,
516554 reason : reasonOverride || 'General Report' ,
517- note : reportNote . trim ( ) || undefined ,
555+ note : playerReportNote . trim ( ) || undefined ,
518556 } , { headers } ) ;
519- setReportMessage ( `✅ Reported ${ reportedPlayer ?. displayName || 'player' } successfully.` ) ;
557+ setPlayerReportMsg ( `✅ Reported ${ reportedPlayer ?. displayName || 'player' } successfully.` ) ;
520558 } catch ( err ) {
521- setReportMessage ( `⚠️ ${ err . response ?. data ?. error || 'Failed to report player.' } ` ) ;
559+ setPlayerReportMsg ( `⚠️ ${ err . response ?. data ?. error || 'Failed to report player.' } ` ) ;
522560 }
523- } , [ reportPlayerId , players , roomCode , reportNote ] ) ;
524-
525- const submitReport = useCallback ( async ( ) => {
526- if ( ! reportPlayerId ) { setReportMessage ( '⚠️ Select a player to report.' ) ; return ; }
527- const trimmed = reportNote . trim ( ) ;
528- const needsNote = reportType === 'inappropriate' || reportType === 'cheating' ;
529- if ( needsNote && ! trimmed ) { setReportMessage ( '⚠️ Please add a short description.' ) ; return ; }
530- const label = reportType === 'inappropriate' ? 'Inappropriate' : reportType === 'cheating' ? 'Cheating' : reportType === 'harassment' ? 'Harassment' : 'General Report' ;
531- await handleReport ( needsNote ? `${ label } : ${ trimmed } ` : label ) ;
532- setShowReportMenu ( false ) ;
533- setReportNote ( '' ) ;
534- setReportType ( 'general' ) ;
561+ } , [ reportPlayerId , players , roomCode , playerReportNote ] ) ;
562+
563+ const submitPlayerReport = useCallback ( async ( ) => {
564+ if ( ! reportPlayerId ) { setPlayerReportMsg ( '⚠️ Select a player to report.' ) ; return ; }
565+ const trimmed = playerReportNote . trim ( ) ;
566+ const needsNote = playerReportType === 'inappropriate' || playerReportType === 'cheating' || playerReportType === 'harassment ';
567+ if ( needsNote && ! trimmed ) { setPlayerReportMsg ( '⚠️ Please add a short description.' ) ; return ; }
568+ const label = playerReportType === 'inappropriate' ? 'Inappropriate' : playerReportType === 'cheating' ? 'Cheating' : playerReportType === 'harassment' ? 'Harassment' : 'General Report' ;
569+ await handlePlayerReport ( needsNote ? `${ label } : ${ trimmed } ` : label ) ;
570+ setShowPlayerReportMenu ( false ) ;
571+ setPlayerReportNote ( '' ) ;
572+ setPlayerReportType ( 'general' ) ;
535573 setReportPlayerId ( '' ) ;
536- } , [ reportNote , reportType , reportPlayerId , handleReport ] ) ;
574+ } , [ playerReportNote , playerReportType , reportPlayerId , handlePlayerReport ] ) ;
537575
538576 const copyCode = useCallback ( ( ) => {
539577 navigator . clipboard ?. writeText ( roomCode ) . then ( ( ) => { setCopied ( true ) ; setTimeout ( ( ) => setCopied ( false ) , 2000 ) ; } ) . catch ( ( ) => { } ) ;
@@ -1163,15 +1201,55 @@ export default function SharePlay() {
11631201 < button
11641202 style = { { ...btn ( '#6c757d' ) , fontSize : '0.85rem' , padding : '7px 14px' } }
11651203 onClick = { ( ) => {
1204+ reportingQIdRef . current = currentQuestion . id ;
11661205 setShowReportMenu ( v => ! v ) ;
11671206 setReportMessage ( '' ) ;
1168- setReportPlayerId ( '' ) ;
11691207 } }
11701208 >
11711209 ⚠ Report
11721210 </ button >
11731211
11741212 { showReportMenu && (
1213+ < 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)' } } >
1214+ < div style = { { fontSize : '0.8rem' , color : '#888' , marginBottom : '6px' } } > Report Question</ div >
1215+ < select value = { reportType } onChange = { e => setReportType ( e . target . value ) }
1216+ 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' } } >
1217+ < option value = "general" > General Report</ option >
1218+ < option value = "inappropriate" > Inappropriate</ option >
1219+ < option value = "incorrect" > Incorrect Answer</ option >
1220+ </ select >
1221+ { ( reportType === 'inappropriate' || reportType === 'incorrect' ) && (
1222+ < textarea value = { reportNote } onChange = { e => setReportNote ( e . target . value ) }
1223+ placeholder = "Add a short description..."
1224+ rows = { 3 }
1225+ 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)' , resize : 'vertical' , boxSizing : 'border-box' , marginBottom : '8px' } } />
1226+ ) }
1227+ { reportMessage && (
1228+ < div style = { { fontSize : '0.8rem' , marginBottom : '8px' , color : reportMessage . startsWith ( '✅' ) ? '#155724' : '#856404' } } >
1229+ { reportMessage }
1230+ </ div >
1231+ ) }
1232+ < div style = { { display : 'flex' , gap : '8px' } } >
1233+ < button style = { { ...btn ( ) , flex : 1 , padding : '7px' , fontSize : '0.85rem' } } onClick = { submitQuestionReport } > Submit</ button >
1234+ < button style = { { ...btn ( '#6c757d' ) , padding : '7px 12px' , fontSize : '0.85rem' } } onClick = { ( ) => { setShowReportMenu ( false ) ; setReportMessage ( '' ) ; } } > Cancel</ button >
1235+ </ div >
1236+ </ div >
1237+ ) }
1238+ </ div >
1239+
1240+ < div style = { { position : 'relative' } } ref = { playerReportRef } >
1241+ < button
1242+ style = { { ...btn ( '#6c757d' ) , fontSize : '0.85rem' , padding : '7px 14px' } }
1243+ onClick = { ( ) => {
1244+ setShowPlayerReportMenu ( v => ! v ) ;
1245+ setPlayerReportMsg ( '' ) ;
1246+ setReportPlayerId ( '' ) ;
1247+ } }
1248+ >
1249+ 🚩 Report Player
1250+ </ button >
1251+
1252+ { showPlayerReportMenu && (
11751253 < 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)' } } >
11761254 < div style = { { fontSize : '0.8rem' , color : '#888' , marginBottom : '6px' } } > Report Player</ div >
11771255 < select value = { reportPlayerId } onChange = { e => setReportPlayerId ( e . target . value ) }
@@ -1181,27 +1259,27 @@ export default function SharePlay() {
11811259 < option key = { p . userId } value = { p . userId } > { p . displayName } </ option >
11821260 ) ) }
11831261 </ select >
1184- < select value = { reportType } onChange = { e => setReportType ( e . target . value ) }
1262+ < select value = { playerReportType } onChange = { e => setPlayerReportType ( e . target . value ) }
11851263 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' } } >
11861264 < option value = "general" > General Report</ option >
11871265 < option value = "inappropriate" > Inappropriate Behavior</ option >
11881266 < option value = "cheating" > Cheating</ option >
11891267 < option value = "harassment" > Harassment</ option >
11901268 </ select >
1191- { ( reportType === 'inappropriate' || reportType === 'cheating' || reportType === 'harassment' ) && (
1192- < textarea value = { reportNote } onChange = { e => setReportNote ( e . target . value ) }
1269+ { ( playerReportType === 'inappropriate' || playerReportType === 'cheating' || playerReportType === 'harassment' ) && (
1270+ < textarea value = { playerReportNote } onChange = { e => setPlayerReportNote ( e . target . value ) }
11931271 placeholder = "Add a short description..."
11941272 rows = { 3 }
11951273 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)' , resize : 'vertical' , boxSizing : 'border-box' , marginBottom : '8px' } } />
11961274 ) }
1197- { reportMessage && (
1198- < div style = { { fontSize : '0.8rem' , marginBottom : '8px' , color : reportMessage . startsWith ( '✅' ) ? '#155724' : '#856404' } } >
1199- { reportMessage }
1275+ { playerReportMsg && (
1276+ < div style = { { fontSize : '0.8rem' , marginBottom : '8px' , color : playerReportMsg . startsWith ( '✅' ) ? '#155724' : '#856404' } } >
1277+ { playerReportMsg }
12001278 </ div >
12011279 ) }
12021280 < div style = { { display : 'flex' , gap : '8px' } } >
1203- < button style = { { ...btn ( ) , flex : 1 , padding : '7px' , fontSize : '0.85rem' } } onClick = { submitReport } > Submit</ button >
1204- < button style = { { ...btn ( '#6c757d' ) , padding : '7px 12px' , fontSize : '0.85rem' } } onClick = { ( ) => { setShowReportMenu ( false ) ; setReportMessage ( '' ) ; } } > Cancel</ button >
1281+ < button style = { { ...btn ( ) , flex : 1 , padding : '7px' , fontSize : '0.85rem' } } onClick = { submitPlayerReport } > Submit</ button >
1282+ < button style = { { ...btn ( '#6c757d' ) , padding : '7px 12px' , fontSize : '0.85rem' } } onClick = { ( ) => { setShowPlayerReportMenu ( false ) ; setPlayerReportMsg ( '' ) ; } } > Cancel</ button >
12051283 </ div >
12061284 </ div >
12071285 ) }
@@ -1210,6 +1288,9 @@ export default function SharePlay() {
12101288 { reportMessage && ! showReportMenu && (
12111289 < span style = { { fontSize : '0.85rem' , color : reportMessage . startsWith ( '✅' ) ? '#155724' : '#856404' } } > { reportMessage } </ span >
12121290 ) }
1291+ { playerReportMsg && ! showPlayerReportMenu && (
1292+ < span style = { { fontSize : '0.85rem' , color : playerReportMsg . startsWith ( '✅' ) ? '#155724' : '#856404' } } > { playerReportMsg } </ span >
1293+ ) }
12131294 </ div >
12141295 ) }
12151296
0 commit comments