"use client";
import { useEffect, useMemo, useState } from "react";
import { useRouter } from "next/navigation";
import Image from "next/image";
import Link from "next/link";
import { usePlaylistsQuery } from "@/hooks/useQueries";
import { useQueryClient } from "@tanstack/react-query";
import { queryKeys } from "@/hooks/useQueries";
import { useAuth } from "@/lib/auth-context";
import { useAudio } from "@/lib/audio-context";
import { Play, Music, Eye, EyeOff } from "lucide-react";
import { GradientSpinner } from "@/components/ui/GradientSpinner";
import { api } from "@/lib/api";
import { cn, isLocalUrl } from "@/utils/cn";
// Lidify brand yellow
const LIDIFY_YELLOW = "#ecb200";
interface PlaylistItem {
id: string;
track: {
album?: {
coverArt?: string;
};
};
}
interface Playlist {
id: string;
name: string;
trackCount?: number;
items?: PlaylistItem[];
isOwner?: boolean;
isHidden?: boolean;
user?: {
username: string;
};
}
// Generate mosaic cover from playlist tracks
function PlaylistMosaic({
items,
size = 4,
greyed = false,
}: {
items?: PlaylistItem[];
size?: number;
greyed?: boolean;
}) {
const coverUrls = useMemo(() => {
if (!items || items.length === 0) return [];
const tracksWithCovers = items.filter(
(item) => item.track?.album?.coverArt
);
if (tracksWithCovers.length === 0) return [];
// Get unique cover arts (up to 4)
const uniqueCovers = Array.from(
new Set(tracksWithCovers.map((item) => item.track.album!.coverArt))
).slice(0, size);
return uniqueCovers.map((cover) => api.getCoverArtUrl(cover!, 200));
}, [items, size]);
if (coverUrls.length === 0) {
return (
);
}
if (coverUrls.length === 1) {
return (
);
}
return (
{coverUrls.slice(0, 4).map((url, index) => (
))}
{Array.from({ length: Math.max(0, 4 - coverUrls.length) }).map(
(_, index) => (
)
)}
);
}
function PlaylistCard({
playlist,
index,
onPlay,
onToggleHide,
isHiddenView = false,
}: {
playlist: Playlist;
index: number;
onPlay: (playlistId: string) => void;
onToggleHide: (playlistId: string, hide: boolean) => void;
isHiddenView?: boolean;
}) {
const isShared = playlist.isOwner === false;
const [isHiding, setIsHiding] = useState(false);
const handleToggleHide = async (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
setIsHiding(true);
try {
await onToggleHide(playlist.id, !playlist.isHidden);
} finally {
setIsHiding(false);
}
};
return (
{/* Cover Image */}
{/* Hide/Unhide button for shared playlists */}
{isShared && (
)}
{/* Play button overlay */}
{/* Title and info */}
{playlist.name}
{isShared && playlist.user?.username ? (
By {playlist.user.username} ยท{" "}
) : null}
{playlist.trackCount || 0}{" "}
{playlist.trackCount === 1 ? "song" : "songs"}
);
}
export default function PlaylistsPage() {
const router = useRouter();
const { isAuthenticated } = useAuth();
const { playTracks } = useAudio();
const queryClient = useQueryClient();
const [showHiddenTab, setShowHiddenTab] = useState(false);
// Use React Query hook for playlists
const { data: playlists = [], isLoading } = usePlaylistsQuery();
// Separate visible and hidden playlists
const { visiblePlaylists, hiddenPlaylists } = useMemo(() => {
const visible: Playlist[] = [];
const hidden: Playlist[] = [];
playlists.forEach((p: Playlist) => {
if (p.isHidden) {
hidden.push(p);
} else {
visible.push(p);
}
});
return { visiblePlaylists: visible, hiddenPlaylists: hidden };
}, [playlists]);
// Listen for playlist events and invalidate cache
useEffect(() => {
const handlePlaylistEvent = () => {
queryClient.invalidateQueries({ queryKey: queryKeys.playlists() });
};
window.addEventListener("playlist-created", handlePlaylistEvent);
window.addEventListener("playlist-updated", handlePlaylistEvent);
window.addEventListener("playlist-deleted", handlePlaylistEvent);
return () => {
window.removeEventListener("playlist-created", handlePlaylistEvent);
window.removeEventListener("playlist-updated", handlePlaylistEvent);
window.removeEventListener("playlist-deleted", handlePlaylistEvent);
};
}, [queryClient]);
const handlePlayPlaylist = async (playlistId: string) => {
try {
const playlist = await api.getPlaylist(playlistId);
if (playlist?.items && playlist.items.length > 0) {
const tracks = playlist.items.map((item: any) => ({
id: item.track.id,
title: item.track.title,
artist: {
name: item.track.album?.artist?.name || "Unknown",
id: item.track.album?.artist?.id,
},
album: {
title: item.track.album?.title || "Unknown",
coverArt: item.track.album?.coverArt,
id: item.track.album?.id,
},
duration: item.track.duration,
}));
playTracks(tracks, 0);
}
} catch (error) {
console.error("Failed to play playlist:", error);
}
};
const handleToggleHide = async (playlistId: string, hide: boolean) => {
try {
if (hide) {
await api.hidePlaylist(playlistId);
} else {
await api.unhidePlaylist(playlistId);
}
// Invalidate and refetch playlists
queryClient.invalidateQueries({ queryKey: queryKeys.playlists() });
} catch (error) {
console.error("Failed to toggle playlist visibility:", error);
}
};
if (isLoading) {
return (
);
}
const displayedPlaylists = showHiddenTab
? hiddenPlaylists
: visiblePlaylists;
return (
{/* Quick gradient fade - yellow to purple */}
{/* Header */}
Playlists
{visiblePlaylists.length}{" "}
{visiblePlaylists.length === 1
? "playlist"
: "playlists"}
{/* Browse Public Playlists */}
Browse Playlists
{/* Hidden Playlists Toggle */}
{hiddenPlaylists.length > 0 && (
)}
{/* Content */}
{/* Hidden playlists notice */}
{showHiddenTab && (
Hidden playlists won't appear in your library. Hover
and click the eye icon to restore.
)}
{displayedPlaylists.length > 0 ? (
{displayedPlaylists.map(
(playlist: Playlist, index: number) => (
)
)}
) : (
{showHiddenTab
? "No hidden playlists"
: "No playlists yet"}
{showHiddenTab
? "You haven't hidden any playlists"
: "Create your first playlist by adding songs from albums or artists"}
{!showHiddenTab && (
Browse Playlists
)}
)}
);
}