'use client'; import { useState, useEffect } from 'react'; interface Song { id: number; title: string; artist: string; filename: string; createdAt: string; activations: number; } type SortField = 'title' | 'artist' | 'createdAt'; type SortDirection = 'asc' | 'desc'; export default function AdminPage() { const [password, setPassword] = useState(''); const [isAuthenticated, setIsAuthenticated] = useState(false); const [file, setFile] = useState(null); const [message, setMessage] = useState(''); const [songs, setSongs] = useState([]); // Edit state const [editingId, setEditingId] = useState(null); const [editTitle, setEditTitle] = useState(''); const [editArtist, setEditArtist] = useState(''); // Sort state const [sortField, setSortField] = useState('artist'); const [sortDirection, setSortDirection] = useState('asc'); // Search and pagination state const [searchQuery, setSearchQuery] = useState(''); const [currentPage, setCurrentPage] = useState(1); const itemsPerPage = 10; // Audio state const [playingSongId, setPlayingSongId] = useState(null); const [audioElement, setAudioElement] = useState(null); // Check for existing auth on mount useEffect(() => { const authToken = localStorage.getItem('hoerdle_admin_auth'); if (authToken === 'authenticated') { setIsAuthenticated(true); fetchSongs(); } }, []); const handleLogin = async () => { const res = await fetch('/api/admin/login', { method: 'POST', body: JSON.stringify({ password }), }); if (res.ok) { localStorage.setItem('hoerdle_admin_auth', 'authenticated'); setIsAuthenticated(true); fetchSongs(); } else { alert('Wrong password'); } }; const fetchSongs = async () => { const res = await fetch('/api/songs'); if (res.ok) { const data = await res.json(); setSongs(data); } }; const handleUpload = async (e: React.FormEvent) => { e.preventDefault(); if (!file) return; const formData = new FormData(); formData.append('file', file); setMessage('Uploading...'); const res = await fetch('/api/songs', { method: 'POST', body: formData, }); if (res.ok) { const data = await res.json(); const validation = data.validation; let statusMessage = 'βœ… Song uploaded successfully!\n\n'; statusMessage += `πŸ“Š Audio Info:\n`; statusMessage += `β€’ Format: ${validation.codec || 'unknown'}\n`; statusMessage += `β€’ Bitrate: ${Math.round(validation.bitrate / 1000)} kbps\n`; statusMessage += `β€’ Sample Rate: ${validation.sampleRate} Hz\n`; statusMessage += `β€’ Duration: ${Math.round(validation.duration)} seconds\n`; statusMessage += `β€’ Cover Art: ${validation.hasCover ? 'βœ… Yes' : '❌ No'}\n`; if (validation.warnings.length > 0) { statusMessage += `\n⚠️ Warnings:\n`; validation.warnings.forEach((warning: string) => { statusMessage += `β€’ ${warning}\n`; }); } setMessage(statusMessage); setFile(null); fetchSongs(); } else { setMessage('Upload failed.'); } }; const startEditing = (song: Song) => { setEditingId(song.id); setEditTitle(song.title); setEditArtist(song.artist); }; const cancelEditing = () => { setEditingId(null); setEditTitle(''); setEditArtist(''); }; const saveEditing = async (id: number) => { const res = await fetch('/api/songs', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id, title: editTitle, artist: editArtist }), }); if (res.ok) { setEditingId(null); fetchSongs(); } else { alert('Failed to update song'); } }; const handleDelete = async (id: number, title: string) => { if (!confirm(`Are you sure you want to delete "${title}"? This will also delete the file.`)) { return; } const res = await fetch('/api/songs', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id }), }); if (res.ok) { fetchSongs(); } else { alert('Failed to delete song'); } }; const handleSort = (field: SortField) => { if (sortField === field) { setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc'); } else { setSortField(field); setSortDirection('asc'); } }; const handlePlayPause = (song: Song) => { if (playingSongId === song.id) { // Pause current song audioElement?.pause(); setPlayingSongId(null); } else { // Stop any currently playing song audioElement?.pause(); // Play new song const audio = new Audio(`/uploads/${song.filename}`); // Handle playback errors audio.onerror = () => { alert(`Failed to load audio file: ${song.filename}\nThe file may be corrupted or missing.`); setPlayingSongId(null); setAudioElement(null); }; audio.play() .then(() => { setAudioElement(audio); setPlayingSongId(song.id); }) .catch((error) => { console.error('Playback error:', error); alert(`Failed to play audio: ${error.message}`); setPlayingSongId(null); setAudioElement(null); }); // Reset when song ends audio.onended = () => { setPlayingSongId(null); setAudioElement(null); }; } }; // Filter and sort songs const filteredSongs = songs.filter(song => song.title.toLowerCase().includes(searchQuery.toLowerCase()) || song.artist.toLowerCase().includes(searchQuery.toLowerCase()) ); const sortedSongs = [...filteredSongs].sort((a, b) => { const valA = a[sortField].toLowerCase(); const valB = b[sortField].toLowerCase(); if (valA < valB) return sortDirection === 'asc' ? -1 : 1; if (valA > valB) return sortDirection === 'asc' ? 1 : -1; return 0; }); // Pagination const totalPages = Math.ceil(sortedSongs.length / itemsPerPage); const startIndex = (currentPage - 1) * itemsPerPage; const paginatedSongs = sortedSongs.slice(startIndex, startIndex + itemsPerPage); // Reset to page 1 when search changes useEffect(() => { setCurrentPage(1); }, [searchQuery]); if (!isAuthenticated) { return (

Admin Login

setPassword(e.target.value)} className="form-input" style={{ marginBottom: '1rem', maxWidth: '300px' }} placeholder="Password" />
); } return (

Admin Dashboard

Upload New Song

setFile(e.target.files?.[0] || null)} className="form-input" required />
{message && (
{message}
)}

Song Library ({songs.length} songs)

{/* Search */}
setSearchQuery(e.target.value)} className="form-input" />
{paginatedSongs.map(song => ( {editingId === song.id ? ( <> ) : ( <> )} ))} {paginatedSongs.length === 0 && ( )}
ID handleSort('title')} > Title {sortField === 'title' && (sortDirection === 'asc' ? '↑' : '↓')} handleSort('artist')} > Artist {sortField === 'artist' && (sortDirection === 'asc' ? '↑' : '↓')} handleSort('createdAt')} > Added {sortField === 'createdAt' && (sortDirection === 'asc' ? '↑' : '↓')} Activations Actions
{song.id} setEditTitle(e.target.value)} className="form-input" style={{ padding: '0.25rem' }} /> setEditArtist(e.target.value)} className="form-input" style={{ padding: '0.25rem' }} /> {new Date(song.createdAt).toLocaleDateString('de-DE')} {song.activations}
{song.title} {song.artist} {new Date(song.createdAt).toLocaleDateString('de-DE')} {song.activations}
{searchQuery ? 'No songs found matching your search.' : 'No songs uploaded yet.'}
{/* Pagination */} {totalPages > 1 && (
Page {currentPage} of {totalPages}
)}
); }