diff --git a/web/src/pages/RequestBrowse.tsx b/web/src/pages/RequestBrowse.tsx new file mode 100644 index 00000000..e4da8f8f --- /dev/null +++ b/web/src/pages/RequestBrowse.tsx @@ -0,0 +1,250 @@ +import { Link, useParams, useSearchParams } from "react-router"; +import { ArrowLeft } from "lucide-react"; +import RequestPosterCard from "@/components/RequestPosterCard"; +import { Button } from "@/components/ui/button"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { useDocumentTitle } from "@/hooks/useDocumentTitle"; +import { useCreateMediaRequest, useRequestBrowse } from "@/hooks/queries/requests"; +import { requestInputFromMediaResult } from "@/lib/mediaRequests"; +import { cn } from "@/lib/utils"; +import type { + DiscoverBrowseKind, + DiscoverBrowseResponse, + RequestMediaResult, + RequestMediaType, +} from "@/api/types"; + +type BrowseSort = "popularity" | "vote_average" | "release_date"; + +const SORT_OPTIONS: { value: BrowseSort; label: string }[] = [ + { value: "popularity", label: "Popularity" }, + { value: "vote_average", label: "Rating" }, + { value: "release_date", label: "Release date" }, +]; + +interface RequestBrowseProps { + kind: DiscoverBrowseKind; +} + +export default function RequestBrowse({ kind }: RequestBrowseProps) { + const { slug = "" } = useParams<{ slug: string }>(); + const [searchParams, setSearchParams] = useSearchParams(); + + const sort = normalizeSort(searchParams.get("sort")); + const page = Math.max(1, Number(searchParams.get("page") ?? "1") || 1); + const mediaTypeFromQuery = normalizeMediaType(searchParams.get("media_type")); + const mediaType: RequestMediaType | undefined = + kind === "studio" ? "movie" : kind === "network" ? "series" : mediaTypeFromQuery ?? "movie"; + + const browse = useRequestBrowse({ kind, slug, mediaType, sort, page }); + const createRequest = useCreateMediaRequest(); + + const title = browse.data?.display_name ?? humanizeSlug(slug); + useDocumentTitle(title ? `${title} - Requests` : "Requests"); + + function updateSort(next: string) { + const params = new URLSearchParams(searchParams); + params.set("sort", next); + params.set("page", "1"); + setSearchParams(params, { replace: true }); + } + + function updateMediaType(next: RequestMediaType) { + const params = new URLSearchParams(searchParams); + params.set("media_type", next); + params.set("page", "1"); + setSearchParams(params, { replace: true }); + } + + function goToPage(next: number) { + const params = new URLSearchParams(searchParams); + params.set("page", String(next)); + setSearchParams(params, { replace: false }); + window.scrollTo({ top: 0, behavior: "smooth" }); + } + + function submitRequest(item: RequestMediaResult) { + createRequest.mutate(requestInputFromMediaResult(item)); + } + + const totalPages = browse.data?.total_pages ?? 0; + const results = browse.data?.results ?? []; + + if (browse.isError && (browse.error as { status?: number }).status === 404) { + return ( +
+ {kind === "studio" ? "Studio" : kind === "network" ? "Network" : "Genre"} not found. +
+ + Back to Requests + ++ {browse.isLoading + ? "Loading..." + : results.length > 0 + ? `Page ${page} of ${totalPages}` + : "No results."} +
++ Could not load this browse page. Try a different sort or media type. +
+ ) : results.length === 0 ? ( +Nothing matched. Try a different sort.
+ ) : ( +