- New modern audio wave 'A' logo (192x192 and 512x512 icons) - PWA service worker for offline support and installability - Wake Lock API for background audio on FiiO/Android devices - Visibility change handling to prevent audio pause on screen off - Updated manifest.json with music categories and proper PWA config - Media Session API lock screen controls (already present) - Renamed app to 'Audiophile Web Player'
102 lines
4.7 KiB
TypeScript
102 lines
4.7 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { Plus, X } from "lucide-react";
|
|
|
|
interface AddToPlaylistModalProps {
|
|
track: any;
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export default function AddToPlaylistModal({ track, isOpen, onClose }: AddToPlaylistModalProps) {
|
|
const [playlists, setPlaylists] = useState<any[]>([]);
|
|
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
const apiUrl = process.env.NEXT_PUBLIC_API_URL || '';
|
|
fetch(`${apiUrl}/api/playlists`)
|
|
.then(res => res.json())
|
|
.then(data => setPlaylists(data))
|
|
.catch(err => console.error(err));
|
|
}
|
|
}, [isOpen]);
|
|
|
|
const handleAddToPlaylist = async (playlistId: string) => {
|
|
try {
|
|
const apiUrl = process.env.NEXT_PUBLIC_API_URL || '';
|
|
await fetch(`${apiUrl}/api/playlists/${playlistId}/tracks`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(track)
|
|
});
|
|
alert(`Added to playlist!`);
|
|
onClose();
|
|
} catch (error) {
|
|
console.error("Failed to add track:", error);
|
|
}
|
|
};
|
|
|
|
if (!isOpen) return null;
|
|
|
|
return (
|
|
<div className="fixed inset-0 bg-black/80 z-[100] flex items-center justify-center p-4">
|
|
<div className="bg-[#282828] w-full max-w-md rounded-lg shadow-2xl overflow-hidden">
|
|
<div className="p-4 border-b border-[#3e3e3e] flex items-center justify-between">
|
|
<h2 className="text-xl font-bold text-white">Add to Playlist</h2>
|
|
<button onClick={onClose} className="text-white hover:text-gray-300">
|
|
<X className="w-6 h-6" />
|
|
</button>
|
|
</div>
|
|
|
|
<div className="p-2 max-h-[60vh] overflow-y-auto no-scrollbar">
|
|
{playlists.length === 0 ? (
|
|
<div className="p-4 text-center text-spotify-text-muted">No playlists found. Create one first!</div>
|
|
) : (
|
|
playlists.map((playlist) => (
|
|
<div
|
|
key={playlist.id}
|
|
onClick={() => handleAddToPlaylist(playlist.id)}
|
|
className="flex items-center gap-3 p-3 hover:bg-[#3e3e3e] rounded-md cursor-pointer transition text-white"
|
|
>
|
|
<div className="w-10 h-10 bg-[#121212] flex items-center justify-center rounded overflow-hidden">
|
|
{playlist.cover_url && !playlist.cover_url.includes("placehold") ? (
|
|
<img src={playlist.cover_url} alt="" className="w-full h-full object-cover" />
|
|
) : (
|
|
<span className="text-lg">🎵</span>
|
|
)}
|
|
</div>
|
|
<span className="font-medium truncate">{playlist.title}</span>
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
|
|
<div className="p-4 border-t border-[#3e3e3e]">
|
|
<button
|
|
onClick={() => {
|
|
const name = prompt("New Playlist Name");
|
|
if (name) {
|
|
const apiUrl = process.env.NEXT_PUBLIC_API_URL || '';
|
|
fetch(`${apiUrl}/api/playlists`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ name })
|
|
}).then(() => {
|
|
// Refresh list
|
|
const apiUrl = process.env.NEXT_PUBLIC_API_URL || '';
|
|
fetch(`${apiUrl}/api/playlists`)
|
|
.then(res => res.json())
|
|
.then(data => setPlaylists(data));
|
|
});
|
|
}
|
|
}}
|
|
className="w-full py-2 bg-white text-black font-bold rounded-full hover:scale-105 transition flex items-center justify-center gap-2"
|
|
>
|
|
<Plus className="w-5 h-5" /> New Playlist
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|