diff --git a/app/admin/page.tsx b/app/admin/page.tsx index 2a45219..42be168 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -97,6 +97,11 @@ export default function AdminPage() { const [playingSongId, setPlayingSongId] = useState(null); const [audioElement, setAudioElement] = useState(null); + // Daily Puzzles state + const [dailyPuzzles, setDailyPuzzles] = useState([]); + const [playingPuzzleId, setPlayingPuzzleId] = useState(null); + const [showDailyPuzzles, setShowDailyPuzzles] = useState(false); + // Check for existing auth on mount useEffect(() => { const authToken = localStorage.getItem('hoerdle_admin_auth'); @@ -104,6 +109,7 @@ export default function AdminPage() { setIsAuthenticated(true); fetchSongs(); fetchGenres(); + fetchDailyPuzzles(); } }, []); @@ -117,6 +123,7 @@ export default function AdminPage() { setIsAuthenticated(true); fetchSongs(); fetchGenres(); + fetchDailyPuzzles(); } else { alert('Wrong password'); } @@ -193,6 +200,63 @@ export default function AdminPage() { else alert('Failed to delete special'); }; + // Daily Puzzles functions + const fetchDailyPuzzles = async () => { + const res = await fetch('/api/admin/daily-puzzles'); + if (res.ok) { + const data = await res.json(); + setDailyPuzzles(data); + } + }; + + const handleDeletePuzzle = async (puzzleId: number) => { + if (!confirm('Delete this daily puzzle? A new one will be generated automatically.')) return; + const res = await fetch('/api/admin/daily-puzzles', { + method: 'DELETE', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ puzzleId }), + }); + if (res.ok) { + fetchDailyPuzzles(); + alert('Puzzle deleted and regenerated successfully'); + } else { + alert('Failed to delete puzzle'); + } + }; + + const handlePlayPuzzle = (puzzle: any) => { + if (playingPuzzleId === puzzle.id) { + // Pause + audioElement?.pause(); + setPlayingPuzzleId(null); + setAudioElement(null); + } else { + // Stop any currently playing audio + if (audioElement) { + audioElement.pause(); + setAudioElement(null); + } + + const audio = new Audio(puzzle.song.audioUrl); + audio.play() + .then(() => { + setAudioElement(audio); + setPlayingPuzzleId(puzzle.id); + }) + .catch((error) => { + console.error('Playback error:', error); + alert(`Failed to play audio: ${error.message}`); + setPlayingPuzzleId(null); + setAudioElement(null); + }); + + audio.onended = () => { + setPlayingPuzzleId(null); + setAudioElement(null); + }; + } + }; + const startEditSpecial = (special: Special) => { setEditingSpecialId(special.id); setEditSpecialName(special.name); @@ -839,13 +903,10 @@ export default function AdminPage() { {message && (
{message}
@@ -853,6 +914,93 @@ export default function AdminPage() { + {/* Today's Daily Puzzles */} +
+
+

+ Today's Daily Puzzles +

+ +
+ {showDailyPuzzles && (dailyPuzzles.length === 0 ? ( +

No daily puzzles found for today.

+ ) : ( + + + + + + + + + + + {dailyPuzzles.map(puzzle => ( + + + + + + + ))} + +
CategorySongArtistActions
+ + {puzzle.category} + + {puzzle.song.title}{puzzle.song.artist} + + +
+ ))} +
+

Song Library ({songs.length} songs) diff --git a/app/api/admin/daily-puzzles/route.ts b/app/api/admin/daily-puzzles/route.ts new file mode 100644 index 0000000..4910b84 --- /dev/null +++ b/app/api/admin/daily-puzzles/route.ts @@ -0,0 +1,105 @@ +import { NextResponse } from 'next/server'; +import { PrismaClient } from '@prisma/client'; + +const prisma = new PrismaClient(); + +export async function GET() { + try { + const today = new Date().toISOString().split('T')[0]; + + const dailyPuzzles = await prisma.dailyPuzzle.findMany({ + where: { + date: today + }, + include: { + song: true, + genre: true, + special: true + }, + orderBy: [ + { genreId: 'asc' }, + { specialId: 'asc' } + ] + }); + + const formattedPuzzles = dailyPuzzles.map(puzzle => ({ + id: puzzle.id, + date: puzzle.date, + category: puzzle.specialId + ? `★ ${puzzle.special?.name}` + : puzzle.genreId + ? `🏷️ ${puzzle.genre?.name}` + : '🌍 Global', + categoryType: puzzle.specialId ? 'special' : puzzle.genreId ? 'genre' : 'global', + genreId: puzzle.genreId, + specialId: puzzle.specialId, + song: { + id: puzzle.song.id, + title: puzzle.song.title, + artist: puzzle.song.artist, + filename: puzzle.song.filename, + audioUrl: `/uploads/${puzzle.song.filename}` + } + })); + + return NextResponse.json(formattedPuzzles); + } catch (error) { + console.error('Error fetching daily puzzles:', error); + return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); + } +} + +export async function DELETE(request: Request) { + try { + const { puzzleId } = await request.json(); + + if (!puzzleId) { + return NextResponse.json({ error: 'Missing puzzleId' }, { status: 400 }); + } + + // Get puzzle details before deletion + const puzzle = await prisma.dailyPuzzle.findUnique({ + where: { id: Number(puzzleId) } + }); + + if (!puzzle) { + return NextResponse.json({ error: 'Puzzle not found' }, { status: 404 }); + } + + // Delete the puzzle + await prisma.dailyPuzzle.delete({ + where: { id: Number(puzzleId) } + }); + + // Regenerate puzzle based on type + const { getOrCreateDailyPuzzle, getOrCreateSpecialPuzzle } = await import('@/lib/dailyPuzzle'); + + let newPuzzle; + if (puzzle.specialId) { + const special = await prisma.special.findUnique({ + where: { id: puzzle.specialId } + }); + if (special) { + newPuzzle = await getOrCreateSpecialPuzzle(special.name); + } + } else if (puzzle.genreId) { + const genre = await prisma.genre.findUnique({ + where: { id: puzzle.genreId } + }); + if (genre) { + newPuzzle = await getOrCreateDailyPuzzle(genre.name); + } + } else { + newPuzzle = await getOrCreateDailyPuzzle(null); + } + + return NextResponse.json({ + success: true, + message: 'Puzzle deleted and regenerated', + newPuzzle + }); + } catch (error) { + console.error('Error deleting puzzle:', error); + return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); + } +}