Liedkampf/client/src/components/BattleResultScreen.jsx
Mathias Wagner 8f91e27ca1
Some checks failed
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 6m59s
Add Battle Result Screen component and associated styles for displaying battle outcomes
2025-05-14 21:53:44 +02:00

199 lines
6.5 KiB
JavaScript

import { useEffect, useState } from 'react';
import { useGame } from '../context/GameContext';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTrophy, faMusic, faCrown, faClock } from '@fortawesome/free-solid-svg-icons';
import YouTubeEmbed from './YouTubeEmbed';
import { getDisplayName } from '../utils/playerUtils';
const BattleResultScreen = () => {
const { lobby, currentPlayer, isHost, proceedToNextBattle } = useGame();
const [countdown, setCountdown] = useState(10); // Auto-advance after 10 seconds
const [showConfetti, setShowConfetti] = useState(true);
// Use previousBattle from the lobby state
const previousBattle = lobby?.previousBattle;
// Extract winning song
const winnerSongId = previousBattle?.winner;
const winningSong = winnerSongId === previousBattle?.song1?.id
? previousBattle?.song1
: previousBattle?.song2;
const losingSong = winnerSongId === previousBattle?.song1?.id
? previousBattle?.song2
: previousBattle?.song1;
const winningVotes = winnerSongId === previousBattle?.song1?.id
? previousBattle?.song1Votes
: previousBattle?.song2Votes;
const losingVotes = winnerSongId === previousBattle?.song1?.id
? previousBattle?.song2Votes
: previousBattle?.song1Votes;
// Get the player who submitted the winning song
const findSubmitter = (song) => {
if (!lobby || !song) return null;
const submitter = lobby.players.find(p => p.id === song.submittedById);
return submitter;
};
const winnerSubmitter = findSubmitter(winningSong);
// YouTube video ID extraction for the winner
const 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;
};
const winnerVideoId = getYouTubeId(winningSong?.youtubeLink);
// Auto-advance countdown
useEffect(() => {
if (countdown > 0) {
const timer = setInterval(() => {
setCountdown(prev => {
if (prev <= 1) {
clearInterval(timer);
// Only auto-advance if host, otherwise wait for host
if (isHost) {
proceedToNextBattle();
}
return 0;
}
return prev - 1;
});
}, 1000);
return () => clearInterval(timer);
}
}, [countdown, isHost, proceedToNextBattle]);
// Confetti effect
useEffect(() => {
if (showConfetti) {
const createConfetti = () => {
const confettiContainer = document.createElement('div');
confettiContainer.className = 'confetti';
// Random position, size, 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)];
const size = Math.random() * 0.5 + 0.3; // Between 0.3 and 0.8rem
const rotation = Math.random() * 360; // Random rotation
const duration = Math.random() * 2 + 1; // Between 1 and 3 seconds
confettiContainer.style.left = `${left}%`;
confettiContainer.style.backgroundColor = color;
confettiContainer.style.width = `${size}rem`;
confettiContainer.style.height = `${size * 0.6}rem`;
confettiContainer.style.transform = `rotate(${rotation}deg)`;
confettiContainer.style.animation = `confetti-fall ${duration}s linear forwards`;
document.querySelector('.battle-result-screen').appendChild(confettiContainer);
// Remove after animation
setTimeout(() => {
confettiContainer.remove();
}, duration * 1000);
};
// Create confetti pieces
const confettiInterval = setInterval(() => {
for (let i = 0; i < 3; i++) {
createConfetti();
}
}, 300);
// Stop confetti after a few seconds
setTimeout(() => {
clearInterval(confettiInterval);
setShowConfetti(false);
}, 4000);
return () => {
clearInterval(confettiInterval);
};
}
}, [showConfetti]);
if (!previousBattle || !winningSong) {
return (
<div className="battle-result-screen">
<h2>Nächster Kampf wird vorbereitet...</h2>
</div>
);
}
return (
<div className="battle-result-screen">
<header>
<h1><FontAwesomeIcon icon={faTrophy} /> Gewinner dieser Runde</h1>
{countdown > 0 && (
<div className="countdown">
<FontAwesomeIcon icon={faClock} />
Nächster Kampf in {countdown}s
</div>
)}
</header>
<div className="winner-announcement">
<div className="song-cards">
<div className="song-card winner">
<div className="victory-badge">
<FontAwesomeIcon icon={faCrown} /> Gewinner
</div>
<div className="song-info">
<h2>{winningSong.title}</h2>
<p className="artist">{winningSong.artist}</p>
{!lobby?.settings?.hidePlayerNames && winnerSubmitter && (
<p className="submitter">
Eingereicht von: {getDisplayName(winnerSubmitter, lobby, currentPlayer)}
</p>
)}
<div className="vote-count">
<span className="votes">{winningVotes} Stimmen</span>
</div>
</div>
{winnerVideoId ? (
<div className="winner-video">
<YouTubeEmbed videoId={winnerVideoId} autoplay={true} />
</div>
) : (
<div className="no-video">
<FontAwesomeIcon icon={faMusic} className="pulse-icon" />
<span>Kein Video verfügbar</span>
</div>
)}
</div>
{losingSong && (
<div className="song-card loser">
<div className="versus">VS</div>
<div className="song-info">
<h3>{losingSong.title}</h3>
<p className="artist">{losingSong.artist}</p>
<div className="vote-count">
<span className="votes">{losingVotes} Stimmen</span>
</div>
</div>
</div>
)}
</div>
</div>
</div>
);
};
export default BattleResultScreen;