Compare commits

..

2 Commits

Author SHA1 Message Date
e24ecb418c Translate UI text to German for song submission and voting screens to enhance localization
All checks were successful
Publish Docker image / Push Docker image to Docker Hub (push) Successful in 1m38s
2025-04-24 21:05:04 +02:00
1a7d7f4df3 Update server port in Vite config and Dockerfile from 5287 to 5237 for consistency 2025-04-24 20:42:49 +02:00
4 changed files with 107 additions and 107 deletions

View File

@ -25,6 +25,6 @@ RUN chown -R node:node /app
ENV NODE_ENV=production ENV NODE_ENV=production
USER node USER node
EXPOSE 5287 EXPOSE 5237
CMD ["node", "index.js"] CMD ["node", "index.js"]

View File

@ -21,7 +21,7 @@ const SongSubmissionScreen = () => {
useEffect(() => { useEffect(() => {
if (lobby) { if (lobby) {
// Find current player's songs // Finde den aktuellen Spieler und seine Lieder
const player = lobby.players.find(p => p.id === currentPlayer.id); const player = lobby.players.find(p => p.id === currentPlayer.id);
if (player) { if (player) {
setIsReady(player.isReady); setIsReady(player.isReady);
@ -29,46 +29,46 @@ const SongSubmissionScreen = () => {
} }
}, [lobby, currentPlayer]); }, [lobby, currentPlayer]);
// Get player's songs from the server // Hole die Lieder des Spielers vom Server
useEffect(() => { useEffect(() => {
const fetchPlayerSongs = async () => { const fetchPlayerSongs = async () => {
if (lobby && currentPlayer) { if (lobby && currentPlayer) {
console.log('Fetching songs for player:', currentPlayer); console.log('Lieder für Spieler abrufen:', currentPlayer);
console.log('All players in lobby:', lobby.players.map(p => ({ id: p.id, name: p.name }))); console.log('Alle Spieler in der Lobby:', lobby.players.map(p => ({ id: p.id, name: p.name })));
// Find the current player by their ID, name, or socket ID // Finde den aktuellen Spieler anhand seiner ID, Name oder Socket-ID
let player = lobby.players.find(p => p.id === currentPlayer.id); let player = lobby.players.find(p => p.id === currentPlayer.id);
// If not found by ID, try by name as fallback // Falls nicht gefunden, versuche es mit dem Namen als Fallback
if (!player) { if (!player) {
player = lobby.players.find(p => p.name === currentPlayer.name); player = lobby.players.find(p => p.name === currentPlayer.name);
console.log('Player not found by ID, trying by name. Found:', player); console.log('Spieler nicht über ID gefunden, versuche es mit Namen. Gefunden:', player);
} }
// If player found and has songs, update the state // Wenn Spieler gefunden und Lieder vorhanden, aktualisiere den Zustand
if (player) { if (player) {
console.log('Found player:', player); console.log('Spieler gefunden:', player);
if (player.songs && Array.isArray(player.songs)) { if (player.songs && Array.isArray(player.songs)) {
console.log('Found player songs for', player.name, ':', player.songs.length); console.log('Spieler-Lieder gefunden für', player.name, ':', player.songs.length);
console.log('Songs data:', player.songs); console.log('Lieder-Daten:', player.songs);
setSongs(player.songs || []); setSongs(player.songs || []);
} else { } else {
console.log('No songs array for player:', player); console.log('Kein Lieder-Array für Spieler:', player);
setSongs([]); setSongs([]);
} }
} else { } else {
console.error('Player not found in lobby! Current player:', currentPlayer.id); console.error('Spieler nicht in Lobby gefunden! Aktueller Spieler:', currentPlayer.id);
console.log('Available players:', lobby.players.map(p => p.id)); console.log('Verfügbare Spieler:', lobby.players.map(p => p.id));
setSongs([]); setSongs([]);
} }
} }
}; };
fetchPlayerSongs(); fetchPlayerSongs();
// Important: This effect should run whenever the lobby state changes // Wichtig: Dieser Effekt sollte ausgeführt werden, wenn sich der Lobby-Zustand ändert
}, [lobby, currentPlayer]); }, [lobby, currentPlayer]);
// Extract video ID from YouTube URL // Extrahiere Video-ID aus YouTube-URL
const extractVideoId = (url) => { const extractVideoId = (url) => {
if (!url) return null; if (!url) return null;
@ -88,7 +88,7 @@ const SongSubmissionScreen = () => {
return null; return null;
}; };
// Debounced search function // Verzögerte Suchfunktion
const handleSearch = async (query) => { const handleSearch = async (query) => {
if (!query.trim()) { if (!query.trim()) {
setSearchResults([]); setSearchResults([]);
@ -100,7 +100,7 @@ const SongSubmissionScreen = () => {
const results = await searchYouTube(query); const results = await searchYouTube(query);
setSearchResults(results || []); setSearchResults(results || []);
} catch (error) { } catch (error) {
console.error('Search failed:', error); console.error('Suche fehlgeschlagen:', error);
} finally { } finally {
setIsSearching(false); setIsSearching(false);
} }
@ -112,45 +112,45 @@ const SongSubmissionScreen = () => {
if (name === 'searchQuery') { if (name === 'searchQuery') {
setSearchQuery(value); setSearchQuery(value);
// Check if the input might be a YouTube link // Prüfe, ob die Eingabe ein YouTube-Link sein könnte
const videoId = extractVideoId(value); const videoId = extractVideoId(value);
if (videoId) { if (videoId) {
setSongForm({ youtubeLink: value }); setSongForm({ youtubeLink: value });
// Set basic information immediately for a responsive UI // Setze grundlegende Informationen sofort für eine reaktionsschnelle Benutzeroberfläche
const basicVideoInfo = { const basicVideoInfo = {
id: videoId, id: videoId,
url: value, url: value,
thumbnail: `https://img.youtube.com/vi/${videoId}/mqdefault.jpg` thumbnail: `https://img.youtube.com/vi/${videoId}/mqdefault.jpg`
}; };
setSelectedVideo(basicVideoInfo); setSelectedVideo(basicVideoInfo);
setSearchResults([]); // Clear any existing search results setSearchResults([]); // Lösche bestehende Suchergebnisse
// Fetch additional metadata from the server // Hole zusätzliche Metadaten vom Server
try { try {
setIsLoadingMetadata(true); setIsLoadingMetadata(true);
const metadata = await getYouTubeMetadata(videoId); const metadata = await getYouTubeMetadata(videoId);
if (metadata) { if (metadata) {
// Update selected video with full metadata // Aktualisiere ausgewähltes Video mit vollständigen Metadaten
setSelectedVideo({ setSelectedVideo({
...basicVideoInfo, ...basicVideoInfo,
title: metadata.title || 'Unknown Title', title: metadata.title || 'Unbekannter Titel',
artist: metadata.artist || 'Unknown Artist', artist: metadata.artist || 'Unbekannter Künstler',
thumbnail: metadata.thumbnail || basicVideoInfo.thumbnail thumbnail: metadata.thumbnail || basicVideoInfo.thumbnail
}); });
} }
} catch (error) { } catch (error) {
console.error('Error fetching video metadata:', error); console.error('Fehler beim Abrufen der Video-Metadaten:', error);
} finally { } finally {
setIsLoadingMetadata(false); setIsLoadingMetadata(false);
} }
} else if (value.trim()) { } else if (value.trim()) {
// Clear any previous timeout // Lösche vorherigen Timeout
if (searchTimeout.current) { if (searchTimeout.current) {
clearTimeout(searchTimeout.current); clearTimeout(searchTimeout.current);
} }
// Set a new timeout to prevent too many API calls // Setze einen neuen Timeout, um zu viele API-Aufrufe zu vermeiden
searchTimeout.current = setTimeout(() => handleSearch(value), 500); searchTimeout.current = setTimeout(() => handleSearch(value), 500);
} else { } else {
setSearchResults([]); setSearchResults([]);
@ -159,7 +159,7 @@ const SongSubmissionScreen = () => {
} }
}; };
// Function to toggle video preview // Funktion zum Umschalten der Videovorschau
const togglePreview = (result) => { const togglePreview = (result) => {
if (selectedVideo && selectedVideo.id === result.id) { if (selectedVideo && selectedVideo.id === result.id) {
setPreviewVisible(!previewVisible); setPreviewVisible(!previewVisible);
@ -170,95 +170,95 @@ const SongSubmissionScreen = () => {
}; };
const handleSelectSearchResult = (result) => { const handleSelectSearchResult = (result) => {
// Make sure we have a complete result with all required fields // Stelle sicher, dass wir ein vollständiges Ergebnis mit allen erforderlichen Feldern haben
const completeResult = { const completeResult = {
id: result.id, id: result.id,
url: result.url || `https://www.youtube.com/watch?v=${result.id}`, url: result.url || `https://www.youtube.com/watch?v=${result.id}`,
title: result.title || 'Unknown Title', title: result.title || 'Unbekannter Titel',
artist: result.artist || 'Unknown Artist', artist: result.artist || 'Unbekannter Künstler',
thumbnail: result.thumbnail || `https://img.youtube.com/vi/${result.id}/mqdefault.jpg` thumbnail: result.thumbnail || `https://img.youtube.com/vi/${result.id}/mqdefault.jpg`
}; };
// When a search result is selected, store the YouTube URL // Wenn ein Suchergebnis ausgewählt wird, speichere die YouTube-URL
setSongForm({ setSongForm({
youtubeLink: completeResult.url youtubeLink: completeResult.url
}); });
// Store the selected video with all necessary data for submission // Speichere das ausgewählte Video mit allen notwendigen Daten zur Übermittlung
setSelectedVideo(completeResult); setSelectedVideo(completeResult);
// Keep the search results visible but update the query field // Behalte die Suchergebnisse sichtbar, aber aktualisiere das Abfragefeld
setSearchQuery(completeResult.title); setSearchQuery(completeResult.title);
}; };
const handleAddSong = (e) => { const handleAddSong = (e) => {
e.preventDefault(); e.preventDefault();
// Use selected video data if available, otherwise fallback to search query or direct input // Verwende die Daten des ausgewählten Videos, wenn verfügbar, sonst Fallback auf Suchanfrage oder direkte Eingabe
let songData; let songData;
// Generate a consistent ID format that will work for deletion later // Erzeuge ein konsistentes ID-Format, das später für die Löschung funktioniert
const generateSongId = () => `song_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const generateSongId = () => `song_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
if (selectedVideo) { if (selectedVideo) {
// We have a selected video with full details - use all available metadata // Wir haben ein ausgewähltes Video mit vollständigen Details - verwende alle verfügbaren Metadaten
songData = { songData = {
youtubeLink: selectedVideo.url, youtubeLink: selectedVideo.url,
title: selectedVideo.title, title: selectedVideo.title,
artist: selectedVideo.artist, artist: selectedVideo.artist,
thumbnail: selectedVideo.thumbnail, thumbnail: selectedVideo.thumbnail,
id: generateSongId() // Consistent ID format id: generateSongId() // Konsistentes ID-Format
}; };
console.log("Adding song with full metadata:", songData); console.log("Füge Lied mit vollständigen Metadaten hinzu:", songData);
} else { } else {
// Extract YouTube URL from search query or direct input // Extrahiere YouTube-URL aus Suchanfrage oder direkter Eingabe
const youtubeLink = searchQuery.trim() || songForm.youtubeLink.trim(); const youtubeLink = searchQuery.trim() || songForm.youtubeLink.trim();
if (!youtubeLink) return; if (!youtubeLink) return;
// Extract video ID to check if it's a valid YouTube link // Extrahiere Video-ID, um zu überprüfen, ob es sich um einen gültigen YouTube-Link handelt
const videoId = extractVideoId(youtubeLink); const videoId = extractVideoId(youtubeLink);
if (videoId) { if (videoId) {
// It's a YouTube link, send it to the server for metadata resolution // Es ist ein YouTube-Link, sende ihn zur Metadaten-Auflösung an den Server
songData = { songData = {
youtubeLink: youtubeLink, youtubeLink: youtubeLink,
// Include the videoId to make server-side processing easier // Füge die videoId hinzu, um die serverseitige Verarbeitung zu erleichtern
videoId: videoId, videoId: videoId,
id: generateSongId() // Consistent ID format id: generateSongId() // Konsistentes ID-Format
}; };
console.log("Adding song with YouTube link:", songData); console.log("Füge Lied mit YouTube-Link hinzu:", songData);
} else { } else {
// Not a YouTube link - treat as a search query // Kein YouTube-Link - behandle als Suchanfrage
alert("Please enter a valid YouTube link or select a search result"); alert("Bitte gib einen gültigen YouTube-Link ein oder wähle ein Suchergebnis aus");
return; return;
} }
} }
// Add the song and update UI when the server responds // Füge das Lied hinzu und aktualisiere die UI, wenn der Server antwortet
// We use the Promise-based approach to wait for the server-generated ID for proper deletion // Wir verwenden den Promise-basierten Ansatz, um auf die vom Server generierte ID für die ordnungsgemäße Löschung zu warten
// Add the song with callback to update songs when the server responds // Füge das Lied mit Callback hinzu, um Lieder zu aktualisieren, wenn der Server antwortet
addSong(songData) addSong(songData)
.then((updatedLobby) => { .then((updatedLobby) => {
console.log('Song added successfully, received updated lobby:', updatedLobby); console.log('Lied erfolgreich hinzugefügt, aktualisierte Lobby erhalten:', updatedLobby);
if (updatedLobby && currentPlayer) { if (updatedLobby && currentPlayer) {
const player = updatedLobby.players.find(p => p.id === currentPlayer.id); const player = updatedLobby.players.find(p => p.id === currentPlayer.id);
if (player && player.songs) { if (player && player.songs) {
console.log('Setting songs from addSong response:', player.songs); console.log('Setze Lieder aus addSong-Antwort:', player.songs);
setSongs([...player.songs]); // Create a new array to ensure state update setSongs([...player.songs]); // Erstelle ein neues Array, um die Zustandsaktualisierung sicherzustellen
} else { } else {
console.warn('Player or songs not found in updated lobby'); console.warn('Spieler oder Lieder nicht in aktualisierter Lobby gefunden');
} }
} else { } else {
console.warn('Missing lobby or currentPlayer in response'); console.warn('Fehlende Lobby oder currentPlayer in der Antwort');
} }
}) })
.catch(error => { .catch(error => {
console.error('Error adding song:', error); console.error('Fehler beim Hinzufügen des Liedes:', error);
}); });
// Reset form // Formular zurücksetzen
setSongForm({ youtubeLink: '' }); setSongForm({ youtubeLink: '' });
setSearchQuery(''); setSearchQuery('');
setSearchResults([]); setSearchResults([]);
@ -277,10 +277,10 @@ const SongSubmissionScreen = () => {
} }
}; };
// Check if we've submitted enough songs // Prüfe, ob wir genügend Lieder eingereicht haben
const canSubmitMoreSongs = lobby && songs.length < lobby.settings.songsPerPlayer; const canSubmitMoreSongs = lobby && songs.length < lobby.settings.songsPerPlayer;
// Extract YouTube video ID from various YouTube URL formats // Extrahiere YouTube-Video-ID aus verschiedenen YouTube-URL-Formaten
const getYoutubeVideoId = (url) => { const getYoutubeVideoId = (url) => {
if (!url) return null; if (!url) return null;
@ -299,7 +299,7 @@ const SongSubmissionScreen = () => {
return null; return null;
}; };
// Get YouTube thumbnail URL from video URL // Hole YouTube-Thumbnail-URL aus Video-URL
const getYoutubeThumbnail = (url) => { const getYoutubeThumbnail = (url) => {
const videoId = getYoutubeVideoId(url); const videoId = getYoutubeVideoId(url);
return videoId ? `https://img.youtube.com/vi/${videoId}/mqdefault.jpg` : null; return videoId ? `https://img.youtube.com/vi/${videoId}/mqdefault.jpg` : null;
@ -323,7 +323,7 @@ const SongSubmissionScreen = () => {
style={{ width: `${(songs.length / (lobby?.settings?.songsPerPlayer || 1)) * 100}%` }} style={{ width: `${(songs.length / (lobby?.settings?.songsPerPlayer || 1)) * 100}%` }}
></div> ></div>
</div> </div>
<p>{songs.length} / {lobby?.settings?.songsPerPlayer || 0} songs</p> <p>{songs.length} / {lobby?.settings?.songsPerPlayer || 0} Lieder</p>
</div> </div>
<div className="songs-list"> <div className="songs-list">
@ -394,7 +394,7 @@ const SongSubmissionScreen = () => {
)} )}
</div> </div>
{/* Show selected video without embedded player */} {/* Zeige ausgewähltes Video ohne eingebetteten Player */}
{selectedVideo && ( {selectedVideo && (
<div className="selected-video"> <div className="selected-video">
<div className="preview-header"> <div className="preview-header">
@ -421,8 +421,8 @@ const SongSubmissionScreen = () => {
</div> </div>
</div> </div>
<div className="selected-info"> <div className="selected-info">
<h4>{selectedVideo.title || 'Unknown Title'}</h4> <h4>{selectedVideo.title || 'Unbekannter Titel'}</h4>
<p>{selectedVideo.artist || 'Unknown Artist'}</p> <p>{selectedVideo.artist || 'Unbekannter Künstler'}</p>
</div> </div>
</div> </div>
</div> </div>
@ -450,8 +450,8 @@ const SongSubmissionScreen = () => {
</div> </div>
</div> </div>
<div className="result-info"> <div className="result-info">
<h4>{result.title || 'Unknown Title'}</h4> <h4>{result.title || 'Unbekannter Titel'}</h4>
<p>{result.artist || 'Unknown Artist'}</p> <p>{result.artist || 'Unbekannter Künstler'}</p>
</div> </div>
</div> </div>
))} ))}

View File

@ -11,34 +11,34 @@ function VotingScreen() {
const [selectedSong, setSelectedSong] = useState(null); const [selectedSong, setSelectedSong] = useState(null);
const [countdown, setCountdown] = useState(null); const [countdown, setCountdown] = useState(null);
// Get current battle // Hole aktuellen Kampf
const battle = lobby?.currentBattle || null; const battle = lobby?.currentBattle || null;
// Calculate the tournament phase based on the round number and total songs // Berechne die Turnierphase basierend auf der Rundennummer und Gesamtliedern
const tournamentPhase = useMemo(() => { const tournamentPhase = useMemo(() => {
if (!lobby || !battle) return ''; if (!lobby || !battle) return '';
// Get total number of songs in the tournament // Hole Gesamtanzahl der Lieder im Turnier
const totalSongs = lobby.songs?.length || 0; const totalSongs = lobby.songs?.length || 0;
if (totalSongs === 0) return 'Preliminaries'; if (totalSongs === 0) return 'Vorrunden';
// Calculate total rounds needed for the tournament // Berechne die insgesamt benötigten Runden für das Turnier
const totalRounds = Math.ceil(Math.log2(totalSongs)); const totalRounds = Math.ceil(Math.log2(totalSongs));
const currentRound = battle.round + 1; const currentRound = battle.round + 1;
const roundsRemaining = totalRounds - currentRound; const roundsRemaining = totalRounds - currentRound;
if (roundsRemaining === 0) return 'Finals'; if (roundsRemaining === 0) return 'Finale';
if (roundsRemaining === 1) return 'Semi-Finals'; if (roundsRemaining === 1) return 'Halbfinale';
if (roundsRemaining === 2) return 'Quarter-Finals'; if (roundsRemaining === 2) return 'Viertelfinale';
return 'Contestant Round'; return 'Vorrunde';
}, [lobby, battle]); }, [lobby, battle]);
// Check if player has already voted // Prüfe, ob der Spieler bereits abgestimmt hat
useEffect(() => { useEffect(() => {
if (battle && battle.votes && currentPlayer) { if (battle && battle.votes && currentPlayer) {
// Check if player's ID exists in votes object // Prüfe, ob die ID des Spielers im Stimmen-Objekt existiert
// Since votes is sent as an object, not a Map // Da Stimmen als Objekt, nicht als Map gesendet werden
const votesObj = battle.votes || {}; const votesObj = battle.votes || {};
setHasVoted(Object.prototype.hasOwnProperty.call(votesObj, currentPlayer.id) || setHasVoted(Object.prototype.hasOwnProperty.call(votesObj, currentPlayer.id) ||
Object.keys(votesObj).includes(currentPlayer.id)); Object.keys(votesObj).includes(currentPlayer.id));
@ -47,22 +47,22 @@ function VotingScreen() {
} }
}, [battle, currentPlayer]); }, [battle, currentPlayer]);
// Handle vote selection // Behandle Stimmenwahl
const handleVoteSelect = (songId) => { const handleVoteSelect = (songId) => {
if (hasVoted) return; if (hasVoted) return;
setSelectedSong(songId); setSelectedSong(songId);
}; };
// Submit final vote // Sende endgültige Stimme
const handleSubmitVote = async () => { const handleSubmitVote = async () => {
if (!selectedSong || hasVoted) return; if (!selectedSong || hasVoted) return;
await submitVote(selectedSong); await submitVote(selectedSong);
// Setting hasVoted is now handled by the useEffect that checks votes // setHasVoted wird jetzt durch den useEffect behandelt, der die Stimmen prüft
}; };
// Get YouTube video IDs from links // Hole YouTube-Video-IDs aus Links
const getYouTubeId = (url) => { const getYouTubeId = (url) => {
if (!url) return null; if (!url) return null;
@ -75,12 +75,12 @@ function VotingScreen() {
if (!battle || !battle.song1) { if (!battle || !battle.song1) {
return ( return (
<div className="voting-screen"> <div className="voting-screen">
<h2>Preparing the next battle...</h2> <h2>Bereite den nächsten Kampf vor...</h2>
</div> </div>
); );
} }
// Handle "bye" rounds where a song advances automatically // Behandle "Freilos"-Runden, in denen ein Lied automatisch weiterkommt
if (battle.bye === true && battle.song1 && !battle.song2) { if (battle.bye === true && battle.song1 && !battle.song2) {
const song1Id = getYouTubeId(battle.song1?.youtubeLink || ''); const song1Id = getYouTubeId(battle.song1?.youtubeLink || '');
@ -88,10 +88,10 @@ function VotingScreen() {
<div className="voting-screen"> <div className="voting-screen">
<header className="voting-header"> <header className="voting-header">
<h1> <h1>
<FontAwesomeIcon icon={faTrophy} /> Automatic Advance <FontAwesomeIcon icon={faTrophy} /> Automatisches Weiterkommen
</h1> </h1>
<div className="round-info"> <div className="round-info">
<span>Round {battle.round + 1}</span> <span>Runde {battle.round + 1}</span>
</div> </div>
</header> </header>
@ -102,7 +102,7 @@ function VotingScreen() {
<h3>{battle.song1.title}</h3> <h3>{battle.song1.title}</h3>
<p>{battle.song1.artist}</p> <p>{battle.song1.artist}</p>
<div className="auto-advance-notice"> <div className="auto-advance-notice">
<p>This song automatically advances to the next round!</p> <p>Dieses Lied kommt automatisch in die nächste Runde!</p>
</div> </div>
</div> </div>
@ -113,7 +113,7 @@ function VotingScreen() {
) : ( ) : (
<div className="no-video"> <div className="no-video">
<FontAwesomeIcon icon={faMusic} className="pulse-icon" /> <FontAwesomeIcon icon={faMusic} className="pulse-icon" />
<span>No video available</span> <span>Kein Video verfügbar</span>
</div> </div>
)} )}
</div> </div>
@ -121,7 +121,7 @@ function VotingScreen() {
<div className="voting-status"> <div className="voting-status">
<button className="btn primary pixelated full-width" onClick={() => submitVote(battle.song1.id)}> <button className="btn primary pixelated full-width" onClick={() => submitVote(battle.song1.id)}>
Continue to Next Battle Weiter zum nächsten Kampf
<span className="pixel-corner tl"></span> <span className="pixel-corner tl"></span>
<span className="pixel-corner tr"></span> <span className="pixel-corner tr"></span>
<span className="pixel-corner bl"></span> <span className="pixel-corner bl"></span>
@ -139,12 +139,12 @@ function VotingScreen() {
<div className="voting-screen"> <div className="voting-screen">
<header className="voting-header"> <header className="voting-header">
<h1> <h1>
<FontAwesomeIcon icon={tournamentPhase === 'Finals' ? faCrown : tournamentPhase === 'Semi-Finals' ? faMedal : faVoteYea} /> <FontAwesomeIcon icon={tournamentPhase === 'Finale' ? faCrown : tournamentPhase === 'Halbfinale' ? faMedal : faVoteYea} />
{tournamentPhase} {tournamentPhase}
</h1> </h1>
<div className="round-info"> <div className="round-info">
<span>Round {battle.round + 1}</span> <span>Runde {battle.round + 1}</span>
{hasVoted && <span className="voted-badge">You voted!</span>} {hasVoted && <span className="voted-badge">Du hast abgestimmt!</span>}
</div> </div>
</header> </header>
@ -167,14 +167,14 @@ function VotingScreen() {
) : ( ) : (
<div className="no-video"> <div className="no-video">
<FontAwesomeIcon icon={faMusic} className="pulse-icon" /> <FontAwesomeIcon icon={faMusic} className="pulse-icon" />
<span>No video available</span> <span>Kein Video verfügbar</span>
</div> </div>
)} )}
{hasVoted && ( {hasVoted && (
<div className="vote-count"> <div className="vote-count">
<span className="vote-number">{battle.song1Votes}</span> <span className="vote-number">{battle.song1Votes}</span>
<span className="vote-text">votes</span> <span className="vote-text">Stimmen</span>
</div> </div>
)} )}
@ -208,14 +208,14 @@ function VotingScreen() {
) : ( ) : (
<div className="no-video"> <div className="no-video">
<FontAwesomeIcon icon={faMusic} className="pulse-icon" /> <FontAwesomeIcon icon={faMusic} className="pulse-icon" />
<span>No video available</span> <span>Kein Video verfügbar</span>
</div> </div>
)} )}
{hasVoted && ( {hasVoted && (
<div className="vote-count"> <div className="vote-count">
<span className="vote-number">{battle.song2Votes}</span> <span className="vote-number">{battle.song2Votes}</span>
<span className="vote-text">votes</span> <span className="vote-text">Stimmen</span>
</div> </div>
)} )}
@ -234,29 +234,29 @@ function VotingScreen() {
onClick={handleSubmitVote} onClick={handleSubmitVote}
disabled={!selectedSong} disabled={!selectedSong}
> >
Cast Vote Abstimmen
</button> </button>
</div> </div>
)} )}
<div className="voting-status"> <div className="voting-status">
<p>{hasVoted ? 'Waiting for other players to vote...' : 'Choose your favorite!'}</p> <p>{hasVoted ? 'Warte auf andere Spieler...' : 'Wähle deinen Favoriten!'}</p>
<div className="votes-count"> <div className="votes-count">
<span>{battle.voteCount || 0}</span> of <span>{lobby?.players?.filter(p => p.isConnected).length || 0}</span> votes <span>{battle.voteCount || 0}</span> von <span>{lobby?.players?.filter(p => p.isConnected).length || 0}</span> Stimmen
</div> </div>
{/* Player voting status list */} {/* Liste der Spielerstimmen */}
<div className="player-votes"> <div className="player-votes">
<h4>Voters</h4> <h4>Abstimmende</h4>
<ul className="players-voted-list"> <ul className="players-voted-list">
{lobby?.players?.filter(p => p.isConnected).map(player => { {lobby?.players?.filter(p => p.isConnected).map(player => {
// Check if this player has voted // Prüfe, ob dieser Spieler abgestimmt hat
const hasPlayerVoted = battle.votes && const hasPlayerVoted = battle.votes &&
Object.keys(battle.votes).includes(player.id); Object.keys(battle.votes).includes(player.id);
return ( return (
<li key={player.id} className={hasPlayerVoted ? 'voted' : 'not-voted'}> <li key={player.id} className={hasPlayerVoted ? 'voted' : 'not-voted'}>
{player.name} {player.id === currentPlayer.id && '(You)'} {player.name} {player.id === currentPlayer.id && '(Du)'}
{hasPlayerVoted && {hasPlayerVoted &&
<FontAwesomeIcon icon={faCheck} className="vote-icon" /> <FontAwesomeIcon icon={faCheck} className="vote-icon" />
} }

View File

@ -12,7 +12,7 @@ export default defineConfig({
server: { server: {
proxy: { proxy: {
"/socket.io": { "/socket.io": {
target: "http://localhost:5287", target: "http://localhost:5237",
changeOrigin: true, changeOrigin: true,
ws: true, ws: true,
}, },