Remove vote on leave/disconnect, require 2+ members, restore vote on sync
All checks were successful
Build & Push Container Image / build (push) Successful in 10s

- Add removeVote (only during VOTING state, preserves revealed votes)
- Remove user's vote on disconnect, room:leave, and kick
- After vote removal, check auto-reveal for remaining members
- Send poker:my-vote on sync so card selection is restored
- Disable voting cards until at least 2 participants are present

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jan Willem Mannaerts 2026-03-05 11:21:45 +01:00
parent 0b713d3858
commit bf5f71aa35
3 changed files with 64 additions and 3 deletions

View file

@ -60,21 +60,32 @@ export default function PokerRoom({ session, issue, user, members, roomId, onSav
setError(payload?.error || 'Unexpected poker error');
}
function onMyVote(payload) {
if (payload.sessionId !== session.id) return;
if (payload.vote !== null && payload.vote !== undefined) {
setMyVote(payload.vote);
}
}
socket.on('poker:vote-update', onVoteUpdate);
socket.on('poker:revealed', onRevealed);
socket.on('poker:saved', onSavedPayload);
socket.on('poker:error', onError);
socket.on('poker:my-vote', onMyVote);
return () => {
socket.off('poker:vote-update', onVoteUpdate);
socket.off('poker:revealed', onRevealed);
socket.off('poker:saved', onSavedPayload);
socket.off('poker:error', onError);
socket.off('poker:my-vote', onMyVote);
};
}, [session.id, socket]);
const canVote = !revealed && members.length >= 2;
function handleVote(value) {
if (revealed) return;
if (!canVote) return;
setMyVote(value);
socket.emit('poker:vote', {
sessionId: session.id,
@ -162,14 +173,18 @@ export default function PokerRoom({ session, issue, user, members, roomId, onSav
</div>
) : (
<div className="space-y-3">
<div className="grid grid-cols-7 gap-2">
{!canVote && members.length < 2 && (
<p className="text-sm text-slate-400 text-center m-0">Waiting for at least 2 participants to start voting...</p>
)}
<div className={`grid grid-cols-7 gap-2${!canVote ? ' opacity-50 pointer-events-none' : ''}`}>
{CARDS.map((card) => {
const isSelected = myVote === card.value;
return (
<button
key={card.value}
onClick={() => handleVote(card.value)}
className="aspect-[2/3] rounded-sm border-2 transition-all flex flex-col items-center justify-center gap-1 hover:scale-105 cursor-pointer bg-transparent"
disabled={!canVote}
className="aspect-[2/3] rounded-sm border-2 transition-all flex flex-col items-center justify-center gap-1 hover:scale-105 cursor-pointer bg-transparent disabled:cursor-not-allowed"
style={{
borderColor: isSelected ? card.color : 'var(--card-border, #e2e8f0)',
backgroundColor: isSelected ? `${card.color}15` : 'transparent'