import { useState } from "react"; import { Link } from "react-router"; import { Film, Sparkles, Tv } from "lucide-react"; import { useCanRequest } from "@/hooks/useCanRequest"; import { useCreateMediaRequest, useRequestSearch } from "@/hooks/queries/useRequests"; import type { RequestMediaResult } from "@/api/types"; import { formatRequestReason, formatRequestStatus, requestInputFromMediaResult, tmdbImageURL, } from "@/lib/mediaRequests"; import { cn } from "@/lib/utils"; import RequestPosterCard from "./RequestPosterCard"; function cardKey(item: Pick): string { return `${item.media_type}-${item.tmdb_id}`; } function nonRequestableLabel(item: RequestMediaResult): string { if (item.request.status) { return formatRequestStatus(item.request.status); } if (item.request.reason) { return formatRequestReason(item.request.reason); } return "Blocked"; } const DIALOG_LIMIT = 4; const GRID_LIMIT = 20; export type RequestToAddSectionProps = { variant: "dialog" | "grid"; query: string; /** True when the library search returned at least one hit. Drives header copy. */ libraryHadHits: boolean; }; export function RequestToAddSection({ variant, query, libraryHadHits }: RequestToAddSectionProps) { const { discoveryEnabled } = useCanRequest(); const search = useRequestSearch("all", query, 1, { enabled: discoveryEnabled, requireProfile: true, staleTime: 5 * 60 * 1000, }); if (!discoveryEnabled) return null; if (search.isError && !search.data) return null; const filtered = (search.data?.results ?? []).filter((item) => item.availability !== "available"); if (filtered.length === 0) return null; const limit = variant === "dialog" ? DIALOG_LIMIT : GRID_LIMIT; const visible = filtered.slice(0, limit); if (variant === "dialog") { return ; } return ; } function HeaderCopy({ libraryHadHits, count }: { libraryHadHits: boolean; count: number }) { if (libraryHadHits) { return (
Request to Add {count}
); } return (
Not in your library, but you can request:
); } function DialogVariant({ items, libraryHadHits, }: { items: RequestMediaResult[]; libraryHadHits: boolean; }) { return (
    {items.map((item) => (
  • ))}
); } function DialogRow({ item }: { item: RequestMediaResult }) { const poster = tmdbImageURL(item.poster_path); const Icon = item.media_type === "series" ? Tv : Film; const requestable = item.request.requestable; const unavailableLabel = requestable ? null : nonRequestableLabel(item); return (
{poster ? ( ) : (
)}
{item.title}
{item.year ? `${item.year} · ` : ""} {item.media_type === "series" ? "Series" : "Movie"}
{requestable ? ( Request ) : ( {unavailableLabel} )} ); } function GridVariant({ items, libraryHadHits, }: { items: RequestMediaResult[]; libraryHadHits: boolean; }) { const count = items.length; const createRequest = useCreateMediaRequest(); // Track each in-flight card key independently; the shared `useMutation` // observer overwrites its `variables` on every `mutate` call, so rapid // clicks on different cards would otherwise trample each other's spinner. const [pendingKeys, setPendingKeys] = useState>(new Set()); const submitCard = (item: RequestMediaResult) => { const key = cardKey(item); setPendingKeys((prev) => { const next = new Set(prev); next.add(key); return next; }); createRequest.mutate(requestInputFromMediaResult(item), { onSettled: () => { setPendingKeys((prev) => { if (!prev.has(key)) return prev; const next = new Set(prev); next.delete(key); return next; }); }, }); }; return (
{libraryHadHits ? "Discover · Outside your library" : "Outside your library"}

{libraryHadHits ? "Request to Add" : "Not in your library, but you can request"}

{count} {count === 1 ? "result" : "results"}
{items.map((item) => { const key = cardKey(item); return ( submitCard(item)} fluid /> ); })}
); }