204 lines
6.1 KiB
JavaScript
204 lines
6.1 KiB
JavaScript
import React, { useState, useEffect, useCallback } from 'react';
|
|
import VotingBoard from '../VotingBoard';
|
|
import BoardCard from '../BoardCard';
|
|
import Results from '../Results';
|
|
import styles from './VotingTable.module.css';
|
|
import titleCardSrc from '../../assets/images/titlecard.png';
|
|
import { ReactComponent as ExitIcon } from '../../assets/images/exit.svg';
|
|
import { useNavigate, useParams } from 'react-router-dom';
|
|
import ProfileMenuButton from '../ProfileMenuButton';
|
|
import api from '../../services/api';
|
|
|
|
const VotingTable = ({ userId }) => {
|
|
const [users, setUsers] = useState([]);
|
|
const [votes, setVotes] = useState({});
|
|
const [, setVotedUsers] = useState(0);
|
|
const [isFinal, setIsFinal] = useState(false);
|
|
const { sessionId } = useParams();
|
|
const [currentUID, setCurrentUID] = useState('');
|
|
const navigate = useNavigate();
|
|
|
|
const maxUsers = 32;
|
|
const username =
|
|
users.find((user) => user.uid === currentUID)?.username || '';
|
|
|
|
const goToLobby = useCallback(() => {
|
|
navigate(`/lobby/${sessionId}`);
|
|
}, [sessionId, navigate]);
|
|
|
|
async function handleLogout() {
|
|
await handleDropUser(currentUID);
|
|
}
|
|
|
|
async function handleInvite() {
|
|
try {
|
|
navigator.clipboard.writeText(window.location.href);
|
|
} catch (err) {
|
|
console.error('Failed to copy invite link: ', err);
|
|
}
|
|
}
|
|
|
|
const handleDropUser = async (userId) => {
|
|
try {
|
|
// Remove user vote and user from session via API calls
|
|
await api.delete(`/api/session/${sessionId}/votes/${userId}`);
|
|
await api.delete(`/api/session/${sessionId}/users/${userId}`);
|
|
|
|
// Fetch updated session data after user removal
|
|
const sessionResponse = await api.get(`/api/session/${sessionId}`);
|
|
const sessionData = sessionResponse.data;
|
|
|
|
if (!sessionData) return;
|
|
|
|
// Update state with the new session data
|
|
const userList = Object.entries(sessionData.users || {}).map(
|
|
([uid, userInfo]) => ({
|
|
uid,
|
|
username: userInfo.username,
|
|
})
|
|
);
|
|
|
|
setUsers(userList);
|
|
setVotes(sessionData.votes || {});
|
|
|
|
// Redirect the dropped user to the lobby
|
|
await api.post(`/api/users/${userId}`, {
|
|
currentSessionId: `lobby/${sessionId}`,
|
|
});
|
|
} catch (error) {
|
|
console.error('Error removing user:', error);
|
|
}
|
|
};
|
|
|
|
const handleFinalizeVoting = async () => {
|
|
try {
|
|
// Finalize voting by setting the isFinal flag in the session via API call
|
|
await api.post(`/api/session/${sessionId}/finalize`, {
|
|
isFinal: true,
|
|
});
|
|
setIsFinal(true);
|
|
} catch (error) {
|
|
console.error('Error finalizing voting:', error);
|
|
}
|
|
};
|
|
|
|
const handleResetVoting = async () => {
|
|
try {
|
|
// Reset the session's votes and isFinal flag via API call
|
|
await api.post(`/api/session/${sessionId}/reset`, {
|
|
votes: {},
|
|
isFinal: false,
|
|
});
|
|
setVotes({});
|
|
setVotedUsers(0);
|
|
setIsFinal(false);
|
|
} catch (error) {
|
|
console.error('Error resetting voting:', error);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
const fetchSessionData = async () => {
|
|
try {
|
|
// Fetch session data from the API
|
|
const sessionResponse = await api.get(`/api/session/${sessionId}`);
|
|
const sessionData = sessionResponse.data;
|
|
|
|
if (!sessionData) {
|
|
// Initialize the session if it doesn't exist
|
|
await api.post(`/api/session/${sessionId}`, {
|
|
votes: {},
|
|
maxUsers: maxUsers,
|
|
isFinal: false,
|
|
users: {},
|
|
});
|
|
} else {
|
|
// Load the existing session data
|
|
const userList = Object.entries(sessionData.users || {}).map(
|
|
([uid, userInfo]) => ({
|
|
uid,
|
|
username: userInfo.username,
|
|
})
|
|
);
|
|
setUsers(userList);
|
|
setVotes(sessionData.votes || {});
|
|
setIsFinal(sessionData.isFinal || false);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching session data:', error);
|
|
goToLobby(); // Redirect to lobby if the session doesn't exist
|
|
}
|
|
};
|
|
|
|
fetchSessionData();
|
|
|
|
// Polling or WebSocket could be used to listen for real-time updates
|
|
const intervalId = setInterval(fetchSessionData, 5000); // Poll every 5 seconds
|
|
return () => clearInterval(intervalId); // Clean up the interval
|
|
}, [sessionId, maxUsers, navigate, setCurrentUID, goToLobby]);
|
|
|
|
const allVoted = Object.values(votes).every((v) => v >= 0);
|
|
|
|
return (
|
|
<div className={styles.votingTable}>
|
|
<ProfileMenuButton
|
|
username={username}
|
|
onLogout={handleLogout}
|
|
onInvite={handleInvite}
|
|
/>
|
|
<img className={styles.titleCard} src={titleCardSrc} alt="title-card" />
|
|
<div className={styles.tableCards}>
|
|
{users.map(({ uid, username }) => (
|
|
<div key={uid} className={styles.userCardWrapper}>
|
|
<BoardCard
|
|
username={username}
|
|
vote={votes[uid]}
|
|
isRevealed={isFinal && votes[uid] !== -1}
|
|
isDimmed={votes[uid] === -1}
|
|
isHighlighted={currentUID === uid}
|
|
/>
|
|
{
|
|
<ExitIcon
|
|
className={styles.dropButton}
|
|
onClick={
|
|
uid === currentUID ? handleLogout : () => handleDropUser(uid)
|
|
}
|
|
title="Drop User"
|
|
/>
|
|
}
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className={styles.form}>
|
|
{allVoted && !isFinal && (
|
|
<div
|
|
className={`${styles.reveal} ${styles.button}`}
|
|
onClick={handleFinalizeVoting}
|
|
>
|
|
Reveal Votes
|
|
</div>
|
|
)}
|
|
{isFinal && (
|
|
<div
|
|
className={`${styles.restart} ${styles.button}`}
|
|
onClick={handleResetVoting}
|
|
>
|
|
Restart Voting
|
|
</div>
|
|
)}
|
|
</div>
|
|
{!isFinal ? (
|
|
<VotingBoard
|
|
sessionId={sessionId}
|
|
userId={userId}
|
|
maxUsers={maxUsers}
|
|
/>
|
|
) : (
|
|
<Results votes={votes} />
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default VotingTable;
|