Add poker:leave on unmount and poker:kick to remove ghost participants
All checks were successful
Build & Push Container Image / build (push) Successful in 9s

Frontend now emits poker:leave when PokerRoom unmounts, preventing
ghost participants. Also adds poker:kick socket event so any session
participant can remove a stale user — shows a small X button next to
each participant. Fixes deadlocked sessions where a disconnected user
blocks reveal (votes.size can never equal participants.size).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jan Willem Mannaerts 2026-03-03 13:54:26 +01:00
parent a7aac985d2
commit 3051119405
2 changed files with 45 additions and 7 deletions

View file

@ -80,6 +80,7 @@ export default function PokerRoom({ session, issue, user, onSaved }) {
}
return () => {
socket.emit('poker:leave', { sessionId: session.id });
socket.off('poker:participants', onParticipants);
socket.off('poker:vote-update', onVoteUpdate);
socket.off('poker:revealed', onRevealed);
@ -98,6 +99,10 @@ export default function PokerRoom({ session, issue, user, onSaved }) {
});
}
function handleKick(userKey) {
socket.emit('poker:kick', { sessionId: session.id, userKey });
}
function handleSave() {
const estimate = Number(manualEstimate || suggestedEstimate);
if (!Number.isFinite(estimate)) {
@ -229,13 +234,24 @@ export default function PokerRoom({ session, issue, user, onSaved }) {
{isCurrentUser && <span className="text-slate-400"> (you)</span>}
</span>
</div>
<div className="text-sm shrink-0 ml-2">
{revealed && vote !== null && vote !== undefined ? (
<span className="font-semibold text-purple-600 dark:text-purple-400">{vote}</span>
) : hasVoted ? (
<span className="text-green-600 dark:text-green-400"></span>
) : (
<span className="text-slate-400">...</span>
<div className="flex items-center gap-2 shrink-0 ml-2">
<span className="text-sm">
{revealed && vote !== null && vote !== undefined ? (
<span className="font-semibold text-purple-600 dark:text-purple-400">{vote}</span>
) : hasVoted ? (
<span className="text-green-600 dark:text-green-400"></span>
) : (
<span className="text-slate-400">...</span>
)}
</span>
{!isCurrentUser && (
<button
onClick={() => handleKick(participant.userKey)}
className="text-slate-400 hover:text-red-500 dark:hover:text-red-400 text-xs cursor-pointer bg-transparent border-none p-0"
title="Remove participant"
>
</button>
)}
</div>
</div>