Compare commits
2 Commits
f9d63c6c49
...
e24ecb418c
Author | SHA1 | Date | |
---|---|---|---|
e24ecb418c | |||
1a7d7f4df3 |
@ -25,6 +25,6 @@ RUN chown -R node:node /app
|
||||
ENV NODE_ENV=production
|
||||
|
||||
USER node
|
||||
EXPOSE 5287
|
||||
EXPOSE 5237
|
||||
|
||||
CMD ["node", "index.js"]
|
@ -21,7 +21,7 @@ const SongSubmissionScreen = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (lobby) {
|
||||
// Find current player's songs
|
||||
// Finde den aktuellen Spieler und seine Lieder
|
||||
const player = lobby.players.find(p => p.id === currentPlayer.id);
|
||||
if (player) {
|
||||
setIsReady(player.isReady);
|
||||
@ -29,46 +29,46 @@ const SongSubmissionScreen = () => {
|
||||
}
|
||||
}, [lobby, currentPlayer]);
|
||||
|
||||
// Get player's songs from the server
|
||||
// Hole die Lieder des Spielers vom Server
|
||||
useEffect(() => {
|
||||
const fetchPlayerSongs = async () => {
|
||||
if (lobby && currentPlayer) {
|
||||
console.log('Fetching songs for player:', currentPlayer);
|
||||
console.log('All players in lobby:', lobby.players.map(p => ({ id: p.id, name: p.name })));
|
||||
console.log('Lieder für Spieler abrufen:', currentPlayer);
|
||||
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);
|
||||
|
||||
// If not found by ID, try by name as fallback
|
||||
// Falls nicht gefunden, versuche es mit dem Namen als Fallback
|
||||
if (!player) {
|
||||
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) {
|
||||
console.log('Found player:', player);
|
||||
console.log('Spieler gefunden:', player);
|
||||
if (player.songs && Array.isArray(player.songs)) {
|
||||
console.log('Found player songs for', player.name, ':', player.songs.length);
|
||||
console.log('Songs data:', player.songs);
|
||||
console.log('Spieler-Lieder gefunden für', player.name, ':', player.songs.length);
|
||||
console.log('Lieder-Daten:', player.songs);
|
||||
setSongs(player.songs || []);
|
||||
} else {
|
||||
console.log('No songs array for player:', player);
|
||||
console.log('Kein Lieder-Array für Spieler:', player);
|
||||
setSongs([]);
|
||||
}
|
||||
} else {
|
||||
console.error('Player not found in lobby! Current player:', currentPlayer.id);
|
||||
console.log('Available players:', lobby.players.map(p => p.id));
|
||||
console.error('Spieler nicht in Lobby gefunden! Aktueller Spieler:', currentPlayer.id);
|
||||
console.log('Verfügbare Spieler:', lobby.players.map(p => p.id));
|
||||
setSongs([]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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]);
|
||||
|
||||
// Extract video ID from YouTube URL
|
||||
// Extrahiere Video-ID aus YouTube-URL
|
||||
const extractVideoId = (url) => {
|
||||
if (!url) return null;
|
||||
|
||||
@ -88,7 +88,7 @@ const SongSubmissionScreen = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
// Debounced search function
|
||||
// Verzögerte Suchfunktion
|
||||
const handleSearch = async (query) => {
|
||||
if (!query.trim()) {
|
||||
setSearchResults([]);
|
||||
@ -100,7 +100,7 @@ const SongSubmissionScreen = () => {
|
||||
const results = await searchYouTube(query);
|
||||
setSearchResults(results || []);
|
||||
} catch (error) {
|
||||
console.error('Search failed:', error);
|
||||
console.error('Suche fehlgeschlagen:', error);
|
||||
} finally {
|
||||
setIsSearching(false);
|
||||
}
|
||||
@ -112,45 +112,45 @@ const SongSubmissionScreen = () => {
|
||||
if (name === 'searchQuery') {
|
||||
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);
|
||||
if (videoId) {
|
||||
setSongForm({ youtubeLink: value });
|
||||
|
||||
// Set basic information immediately for a responsive UI
|
||||
// Setze grundlegende Informationen sofort für eine reaktionsschnelle Benutzeroberfläche
|
||||
const basicVideoInfo = {
|
||||
id: videoId,
|
||||
url: value,
|
||||
thumbnail: `https://img.youtube.com/vi/${videoId}/mqdefault.jpg`
|
||||
};
|
||||
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 {
|
||||
setIsLoadingMetadata(true);
|
||||
const metadata = await getYouTubeMetadata(videoId);
|
||||
if (metadata) {
|
||||
// Update selected video with full metadata
|
||||
// Aktualisiere ausgewähltes Video mit vollständigen Metadaten
|
||||
setSelectedVideo({
|
||||
...basicVideoInfo,
|
||||
title: metadata.title || 'Unknown Title',
|
||||
artist: metadata.artist || 'Unknown Artist',
|
||||
title: metadata.title || 'Unbekannter Titel',
|
||||
artist: metadata.artist || 'Unbekannter Künstler',
|
||||
thumbnail: metadata.thumbnail || basicVideoInfo.thumbnail
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching video metadata:', error);
|
||||
console.error('Fehler beim Abrufen der Video-Metadaten:', error);
|
||||
} finally {
|
||||
setIsLoadingMetadata(false);
|
||||
}
|
||||
} else if (value.trim()) {
|
||||
// Clear any previous timeout
|
||||
// Lösche vorherigen Timeout
|
||||
if (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);
|
||||
} else {
|
||||
setSearchResults([]);
|
||||
@ -159,7 +159,7 @@ const SongSubmissionScreen = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Function to toggle video preview
|
||||
// Funktion zum Umschalten der Videovorschau
|
||||
const togglePreview = (result) => {
|
||||
if (selectedVideo && selectedVideo.id === result.id) {
|
||||
setPreviewVisible(!previewVisible);
|
||||
@ -170,95 +170,95 @@ const SongSubmissionScreen = () => {
|
||||
};
|
||||
|
||||
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 = {
|
||||
id: result.id,
|
||||
url: result.url || `https://www.youtube.com/watch?v=${result.id}`,
|
||||
title: result.title || 'Unknown Title',
|
||||
artist: result.artist || 'Unknown Artist',
|
||||
title: result.title || 'Unbekannter Titel',
|
||||
artist: result.artist || 'Unbekannter Künstler',
|
||||
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({
|
||||
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);
|
||||
|
||||
// Keep the search results visible but update the query field
|
||||
// Behalte die Suchergebnisse sichtbar, aber aktualisiere das Abfragefeld
|
||||
setSearchQuery(completeResult.title);
|
||||
};
|
||||
|
||||
const handleAddSong = (e) => {
|
||||
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;
|
||||
|
||||
// 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)}`;
|
||||
|
||||
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 = {
|
||||
youtubeLink: selectedVideo.url,
|
||||
title: selectedVideo.title,
|
||||
artist: selectedVideo.artist,
|
||||
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 {
|
||||
// Extract YouTube URL from search query or direct input
|
||||
// Extrahiere YouTube-URL aus Suchanfrage oder direkter Eingabe
|
||||
const youtubeLink = searchQuery.trim() || songForm.youtubeLink.trim();
|
||||
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);
|
||||
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 = {
|
||||
youtubeLink: youtubeLink,
|
||||
// Include the videoId to make server-side processing easier
|
||||
// Füge die videoId hinzu, um die serverseitige Verarbeitung zu erleichtern
|
||||
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 {
|
||||
// Not a YouTube link - treat as a search query
|
||||
alert("Please enter a valid YouTube link or select a search result");
|
||||
// Kein YouTube-Link - behandle als Suchanfrage
|
||||
alert("Bitte gib einen gültigen YouTube-Link ein oder wähle ein Suchergebnis aus");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the song and update UI when the server responds
|
||||
// We use the Promise-based approach to wait for the server-generated ID for proper deletion
|
||||
// Füge das Lied hinzu und aktualisiere die UI, wenn der Server antwortet
|
||||
// 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)
|
||||
.then((updatedLobby) => {
|
||||
console.log('Song added successfully, received updated lobby:', updatedLobby);
|
||||
console.log('Lied erfolgreich hinzugefügt, aktualisierte Lobby erhalten:', updatedLobby);
|
||||
if (updatedLobby && currentPlayer) {
|
||||
const player = updatedLobby.players.find(p => p.id === currentPlayer.id);
|
||||
if (player && player.songs) {
|
||||
console.log('Setting songs from addSong response:', player.songs);
|
||||
setSongs([...player.songs]); // Create a new array to ensure state update
|
||||
console.log('Setze Lieder aus addSong-Antwort:', player.songs);
|
||||
setSongs([...player.songs]); // Erstelle ein neues Array, um die Zustandsaktualisierung sicherzustellen
|
||||
} else {
|
||||
console.warn('Player or songs not found in updated lobby');
|
||||
console.warn('Spieler oder Lieder nicht in aktualisierter Lobby gefunden');
|
||||
}
|
||||
} else {
|
||||
console.warn('Missing lobby or currentPlayer in response');
|
||||
console.warn('Fehlende Lobby oder currentPlayer in der Antwort');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error adding song:', error);
|
||||
console.error('Fehler beim Hinzufügen des Liedes:', error);
|
||||
});
|
||||
|
||||
// Reset form
|
||||
// Formular zurücksetzen
|
||||
setSongForm({ youtubeLink: '' });
|
||||
setSearchQuery('');
|
||||
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;
|
||||
|
||||
// Extract YouTube video ID from various YouTube URL formats
|
||||
// Extrahiere YouTube-Video-ID aus verschiedenen YouTube-URL-Formaten
|
||||
const getYoutubeVideoId = (url) => {
|
||||
if (!url) return null;
|
||||
|
||||
@ -299,7 +299,7 @@ const SongSubmissionScreen = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
// Get YouTube thumbnail URL from video URL
|
||||
// Hole YouTube-Thumbnail-URL aus Video-URL
|
||||
const getYoutubeThumbnail = (url) => {
|
||||
const videoId = getYoutubeVideoId(url);
|
||||
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}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<p>{songs.length} / {lobby?.settings?.songsPerPlayer || 0} songs</p>
|
||||
<p>{songs.length} / {lobby?.settings?.songsPerPlayer || 0} Lieder</p>
|
||||
</div>
|
||||
|
||||
<div className="songs-list">
|
||||
@ -394,7 +394,7 @@ const SongSubmissionScreen = () => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Show selected video without embedded player */}
|
||||
{/* Zeige ausgewähltes Video ohne eingebetteten Player */}
|
||||
{selectedVideo && (
|
||||
<div className="selected-video">
|
||||
<div className="preview-header">
|
||||
@ -421,8 +421,8 @@ const SongSubmissionScreen = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="selected-info">
|
||||
<h4>{selectedVideo.title || 'Unknown Title'}</h4>
|
||||
<p>{selectedVideo.artist || 'Unknown Artist'}</p>
|
||||
<h4>{selectedVideo.title || 'Unbekannter Titel'}</h4>
|
||||
<p>{selectedVideo.artist || 'Unbekannter Künstler'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -450,8 +450,8 @@ const SongSubmissionScreen = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="result-info">
|
||||
<h4>{result.title || 'Unknown Title'}</h4>
|
||||
<p>{result.artist || 'Unknown Artist'}</p>
|
||||
<h4>{result.title || 'Unbekannter Titel'}</h4>
|
||||
<p>{result.artist || 'Unbekannter Künstler'}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
@ -11,34 +11,34 @@ function VotingScreen() {
|
||||
const [selectedSong, setSelectedSong] = useState(null);
|
||||
const [countdown, setCountdown] = useState(null);
|
||||
|
||||
// Get current battle
|
||||
// Hole aktuellen Kampf
|
||||
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(() => {
|
||||
if (!lobby || !battle) return '';
|
||||
|
||||
// Get total number of songs in the tournament
|
||||
// Hole Gesamtanzahl der Lieder im Turnier
|
||||
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 currentRound = battle.round + 1;
|
||||
const roundsRemaining = totalRounds - currentRound;
|
||||
|
||||
if (roundsRemaining === 0) return 'Finals';
|
||||
if (roundsRemaining === 1) return 'Semi-Finals';
|
||||
if (roundsRemaining === 2) return 'Quarter-Finals';
|
||||
return 'Contestant Round';
|
||||
if (roundsRemaining === 0) return 'Finale';
|
||||
if (roundsRemaining === 1) return 'Halbfinale';
|
||||
if (roundsRemaining === 2) return 'Viertelfinale';
|
||||
return 'Vorrunde';
|
||||
}, [lobby, battle]);
|
||||
|
||||
// Check if player has already voted
|
||||
// Prüfe, ob der Spieler bereits abgestimmt hat
|
||||
useEffect(() => {
|
||||
if (battle && battle.votes && currentPlayer) {
|
||||
// Check if player's ID exists in votes object
|
||||
// Since votes is sent as an object, not a Map
|
||||
// Prüfe, ob die ID des Spielers im Stimmen-Objekt existiert
|
||||
// Da Stimmen als Objekt, nicht als Map gesendet werden
|
||||
const votesObj = battle.votes || {};
|
||||
setHasVoted(Object.prototype.hasOwnProperty.call(votesObj, currentPlayer.id) ||
|
||||
Object.keys(votesObj).includes(currentPlayer.id));
|
||||
@ -47,22 +47,22 @@ function VotingScreen() {
|
||||
}
|
||||
}, [battle, currentPlayer]);
|
||||
|
||||
// Handle vote selection
|
||||
// Behandle Stimmenwahl
|
||||
const handleVoteSelect = (songId) => {
|
||||
if (hasVoted) return;
|
||||
|
||||
setSelectedSong(songId);
|
||||
};
|
||||
|
||||
// Submit final vote
|
||||
// Sende endgültige Stimme
|
||||
const handleSubmitVote = async () => {
|
||||
if (!selectedSong || hasVoted) return;
|
||||
|
||||
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) => {
|
||||
if (!url) return null;
|
||||
|
||||
@ -75,12 +75,12 @@ function VotingScreen() {
|
||||
if (!battle || !battle.song1) {
|
||||
return (
|
||||
<div className="voting-screen">
|
||||
<h2>Preparing the next battle...</h2>
|
||||
<h2>Bereite den nächsten Kampf vor...</h2>
|
||||
</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) {
|
||||
const song1Id = getYouTubeId(battle.song1?.youtubeLink || '');
|
||||
|
||||
@ -88,10 +88,10 @@ function VotingScreen() {
|
||||
<div className="voting-screen">
|
||||
<header className="voting-header">
|
||||
<h1>
|
||||
<FontAwesomeIcon icon={faTrophy} /> Automatic Advance
|
||||
<FontAwesomeIcon icon={faTrophy} /> Automatisches Weiterkommen
|
||||
</h1>
|
||||
<div className="round-info">
|
||||
<span>Round {battle.round + 1}</span>
|
||||
<span>Runde {battle.round + 1}</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@ -102,7 +102,7 @@ function VotingScreen() {
|
||||
<h3>{battle.song1.title}</h3>
|
||||
<p>{battle.song1.artist}</p>
|
||||
<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>
|
||||
|
||||
@ -113,7 +113,7 @@ function VotingScreen() {
|
||||
) : (
|
||||
<div className="no-video">
|
||||
<FontAwesomeIcon icon={faMusic} className="pulse-icon" />
|
||||
<span>No video available</span>
|
||||
<span>Kein Video verfügbar</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -121,7 +121,7 @@ function VotingScreen() {
|
||||
|
||||
<div className="voting-status">
|
||||
<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 tr"></span>
|
||||
<span className="pixel-corner bl"></span>
|
||||
@ -139,12 +139,12 @@ function VotingScreen() {
|
||||
<div className="voting-screen">
|
||||
<header className="voting-header">
|
||||
<h1>
|
||||
<FontAwesomeIcon icon={tournamentPhase === 'Finals' ? faCrown : tournamentPhase === 'Semi-Finals' ? faMedal : faVoteYea} />
|
||||
<FontAwesomeIcon icon={tournamentPhase === 'Finale' ? faCrown : tournamentPhase === 'Halbfinale' ? faMedal : faVoteYea} />
|
||||
{tournamentPhase}
|
||||
</h1>
|
||||
<div className="round-info">
|
||||
<span>Round {battle.round + 1}</span>
|
||||
{hasVoted && <span className="voted-badge">You voted!</span>}
|
||||
<span>Runde {battle.round + 1}</span>
|
||||
{hasVoted && <span className="voted-badge">Du hast abgestimmt!</span>}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@ -167,14 +167,14 @@ function VotingScreen() {
|
||||
) : (
|
||||
<div className="no-video">
|
||||
<FontAwesomeIcon icon={faMusic} className="pulse-icon" />
|
||||
<span>No video available</span>
|
||||
<span>Kein Video verfügbar</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasVoted && (
|
||||
<div className="vote-count">
|
||||
<span className="vote-number">{battle.song1Votes}</span>
|
||||
<span className="vote-text">votes</span>
|
||||
<span className="vote-text">Stimmen</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -208,14 +208,14 @@ function VotingScreen() {
|
||||
) : (
|
||||
<div className="no-video">
|
||||
<FontAwesomeIcon icon={faMusic} className="pulse-icon" />
|
||||
<span>No video available</span>
|
||||
<span>Kein Video verfügbar</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasVoted && (
|
||||
<div className="vote-count">
|
||||
<span className="vote-number">{battle.song2Votes}</span>
|
||||
<span className="vote-text">votes</span>
|
||||
<span className="vote-text">Stimmen</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -234,29 +234,29 @@ function VotingScreen() {
|
||||
onClick={handleSubmitVote}
|
||||
disabled={!selectedSong}
|
||||
>
|
||||
Cast Vote
|
||||
Abstimmen
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<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">
|
||||
<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>
|
||||
|
||||
{/* Player voting status list */}
|
||||
{/* Liste der Spielerstimmen */}
|
||||
<div className="player-votes">
|
||||
<h4>Voters</h4>
|
||||
<h4>Abstimmende</h4>
|
||||
<ul className="players-voted-list">
|
||||
{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 &&
|
||||
Object.keys(battle.votes).includes(player.id);
|
||||
|
||||
return (
|
||||
<li key={player.id} className={hasPlayerVoted ? 'voted' : 'not-voted'}>
|
||||
{player.name} {player.id === currentPlayer.id && '(You)'}
|
||||
{player.name} {player.id === currentPlayer.id && '(Du)'}
|
||||
{hasPlayerVoted &&
|
||||
<FontAwesomeIcon icon={faCheck} className="vote-icon" />
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ export default defineConfig({
|
||||
server: {
|
||||
proxy: {
|
||||
"/socket.io": {
|
||||
target: "http://localhost:5287",
|
||||
target: "http://localhost:5237",
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user