Add screens and components for Song Battle game, including Home, Lobby, Voting, Results, and Song Submission screens; implement YouTube video embedding and styles
Some checks failed
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 7m12s
Some checks failed
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 7m12s
This commit is contained in:
160
client/src/components/ResultsScreen.jsx
Normal file
160
client/src/components/ResultsScreen.jsx
Normal file
@ -0,0 +1,160 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useGame } from '../context/GameContext';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faTrophy, faHome, faRedo, faChartLine } from '@fortawesome/free-solid-svg-icons';
|
||||
import YouTubeEmbed from './YouTubeEmbed';
|
||||
|
||||
const ResultsScreen = () => {
|
||||
const { lobby, currentPlayer, leaveLobby } = useGame();
|
||||
const [showBattleHistory, setShowBattleHistory] = useState(false);
|
||||
const [confetti, setConfetti] = useState(true);
|
||||
|
||||
// Winner information
|
||||
const winner = lobby?.finalWinner;
|
||||
const winnerVideoId = getYouTubeId(winner?.youtubeLink);
|
||||
|
||||
// Confetti effect for winner celebration
|
||||
useEffect(() => {
|
||||
if (confetti) {
|
||||
// Create confetti animation
|
||||
const createConfetti = () => {
|
||||
const confettiContainer = document.createElement('div');
|
||||
confettiContainer.className = 'confetti';
|
||||
|
||||
// Random position and color
|
||||
const left = Math.random() * 100;
|
||||
const colors = ['#f94144', '#f3722c', '#f8961e', '#f9c74f', '#90be6d', '#43aa8b', '#577590'];
|
||||
const color = colors[Math.floor(Math.random() * colors.length)];
|
||||
|
||||
confettiContainer.style.left = `${left}%`;
|
||||
confettiContainer.style.backgroundColor = color;
|
||||
|
||||
document.querySelector('.results-screen').appendChild(confettiContainer);
|
||||
|
||||
// Remove after animation
|
||||
setTimeout(() => {
|
||||
confettiContainer.remove();
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
// Create multiple confetti pieces
|
||||
const confettiInterval = setInterval(() => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
createConfetti();
|
||||
}
|
||||
}, 300);
|
||||
|
||||
// Stop confetti after some time
|
||||
setTimeout(() => {
|
||||
clearInterval(confettiInterval);
|
||||
setConfetti(false);
|
||||
}, 10000);
|
||||
|
||||
return () => {
|
||||
clearInterval(confettiInterval);
|
||||
};
|
||||
}
|
||||
}, [confetti]);
|
||||
|
||||
// Get YouTube video ID from link
|
||||
function getYouTubeId(url) {
|
||||
if (!url) return null;
|
||||
|
||||
const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
|
||||
const match = url.match(regExp);
|
||||
|
||||
return (match && match[2].length === 11) ? match[2] : null;
|
||||
}
|
||||
|
||||
if (!winner) {
|
||||
return (
|
||||
<div className="results-screen">
|
||||
<h2>Calculating results...</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="results-screen">
|
||||
<header>
|
||||
<h1><FontAwesomeIcon icon={faTrophy} /> Winner!</h1>
|
||||
</header>
|
||||
|
||||
<div className="winner-card">
|
||||
<div className="winner-info">
|
||||
<h2>{winner.title}</h2>
|
||||
<h3>by {winner.artist}</h3>
|
||||
<p className="submitter">Submitted by: {winner.submittedByName}</p>
|
||||
</div>
|
||||
|
||||
{winnerVideoId ? (
|
||||
<div className="winner-video">
|
||||
<YouTubeEmbed videoId={winnerVideoId} autoplay={true} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="winner-placeholder">
|
||||
<FontAwesomeIcon icon={faTrophy} size="3x" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="results-actions">
|
||||
<button
|
||||
className="btn secondary"
|
||||
onClick={() => setShowBattleHistory(!showBattleHistory)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faChartLine} />
|
||||
{showBattleHistory ? 'Hide Battle History' : 'Show Battle History'}
|
||||
</button>
|
||||
|
||||
<button className="btn primary" onClick={leaveLobby}>
|
||||
<FontAwesomeIcon icon={faHome} /> Return to Home
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{showBattleHistory && (
|
||||
<div className="battle-history">
|
||||
<h3>Battle History</h3>
|
||||
|
||||
<div className="battles-list">
|
||||
{lobby?.battles?.map((battle, index) => {
|
||||
const isWinner = (battle.song1.id === battle.winner);
|
||||
const song1VideoId = getYouTubeId(battle.song1.youtubeLink);
|
||||
const song2VideoId = getYouTubeId(battle.song2.youtubeLink);
|
||||
|
||||
return (
|
||||
<div key={index} className="battle-item">
|
||||
<div className="battle-header">
|
||||
<h4>Round {battle.round + 1}, Battle {index + 1}</h4>
|
||||
</div>
|
||||
|
||||
<div className="battle-songs">
|
||||
<div className={`battle-song ${isWinner ? 'winner' : ''}`}>
|
||||
<div className="song-info">
|
||||
<h5>{battle.song1.title}</h5>
|
||||
<p>{battle.song1.artist}</p>
|
||||
<span className="votes">{battle.song1Votes} votes</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="versus">VS</div>
|
||||
|
||||
<div className={`battle-song ${!isWinner ? 'winner' : ''}`}>
|
||||
<div className="song-info">
|
||||
<h5>{battle.song2.title}</h5>
|
||||
<p>{battle.song2.artist}</p>
|
||||
<span className="votes">{battle.song2Votes} votes</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResultsScreen;
|
Reference in New Issue
Block a user