Files
spotizerr-dev/spotizerr-ui/src/components/SearchResultCard.tsx

83 lines
3.2 KiB
TypeScript

import { Link } from "@tanstack/react-router";
import { useContext, useEffect } from "react";
import { toast } from "sonner";
import { QueueContext, getStatus } from "../contexts/queue-context";
interface SearchResultCardProps {
id: string;
name: string;
subtitle?: string;
imageUrl?: string;
type: "track" | "album" | "artist" | "playlist";
onDownload?: () => void;
}
export const SearchResultCard = ({ id, name, subtitle, imageUrl, type, onDownload }: SearchResultCardProps) => {
const context = useContext(QueueContext);
if (!context) throw new Error("useQueue must be used within a QueueProvider");
const { items } = context;
const queueItem = items.find(item => item.downloadType === type && item.spotifyId === id);
const status = queueItem ? getStatus(queueItem) : null;
useEffect(() => {
if (status === "queued") {
toast.success(`${name} queued.`);
} else if (status === "error") {
toast.error(`Failed to queue ${name}`);
}
}, [status]);
const getLinkPath = () => {
switch (type) {
case "track":
return `/track/${id}`;
case "album":
return `/album/${id}`;
case "artist":
return `/artist/${id}`;
case "playlist":
return `/playlist/${id}`;
}
};
return (
<div className="group flex flex-col rounded-lg overflow-hidden bg-surface dark:bg-surface-secondary-dark hover:bg-surface-secondary dark:hover:bg-surface-muted-dark shadow-xl hover:shadow-2xl transition-shadow duration-300 ease-in-out">
<div className="relative">
<Link to={getLinkPath()} className="block">
<img src={imageUrl || "/placeholder.jpg"} alt={name} className="w-full aspect-square object-cover hover:scale-105 transition-transform duration-300" />
</Link>
{onDownload && (
<button
onClick={onDownload}
disabled={!!status && status !== "error"}
className="absolute bottom-2 right-2 p-2 bg-button-success hover:bg-button-success-hover text-button-success-text rounded-full transition-opacity shadow-lg opacity-100 sm:opacity-0 sm:group-hover:opacity-100 duration-300 z-10 disabled:opacity-50 disabled:cursor-not-allowed"
title={
status
? status === "queued"
? `${name} queued`
: status === "error"
? `Download ${type}`
: "Downloading..."
: `Download ${type}`
}
>
{status
? status === "queued"
? "Queued."
: status === "error"
? <img src="/download.svg" alt="Download" className="w-5 h-5 logo" />
: <img src="/spinner.svg" alt="Loading" className="w-5 h-5 animate-spin" />
: <img src="/download.svg" alt="Download" className="w-5 h-5 logo" />
}
</button>
)}
</div>
<div className="p-4 flex-grow flex flex-col">
<Link to={getLinkPath()} className="font-semibold text-content-primary dark:text-content-primary-dark truncate block">
{name}
</Link>
{subtitle && <p className="text-sm text-content-secondary dark:text-content-secondary-dark mt-1 truncate">{subtitle}</p>}
</div>
</div>
);
};