mirror of
https://github.com/movie-web/movie-web.git
synced 2025-01-12 22:29:10 +01:00
214 lines
6.0 KiB
TypeScript
214 lines
6.0 KiB
TypeScript
import { WatchedMediaCard } from "components/media/WatchedMediaCard";
|
|
import { SearchBarInput } from "components/SearchBar";
|
|
import { MWMassProviderOutput, MWQuery, SearchProviders } from "providers";
|
|
import { useEffect, useMemo, useState } from "react";
|
|
import { ThinContainer } from "components/layout/ThinContainer";
|
|
import { SectionHeading } from "components/layout/SectionHeading";
|
|
import { Icons } from "components/Icon";
|
|
import { Loading } from "components/layout/Loading";
|
|
import { Tagline } from "components/text/Tagline";
|
|
import { Title } from "components/text/Title";
|
|
import { useDebounce } from "hooks/useDebounce";
|
|
import { useLoading } from "hooks/useLoading";
|
|
import { IconPatch } from "components/buttons/IconPatch";
|
|
import { Navigation } from "components/layout/Navigation";
|
|
import { useSearchQuery } from "hooks/useSearchQuery";
|
|
import { useWatchedContext } from "state/watched/context";
|
|
import {
|
|
getIfBookmarkedFromPortable,
|
|
useBookmarkContext,
|
|
} from "state/bookmark/context";
|
|
|
|
function SearchLoading() {
|
|
return <Loading className="my-24" text="Fetching your favourite shows..." />;
|
|
}
|
|
|
|
function SearchSuffix(props: {
|
|
fails: number;
|
|
total: number;
|
|
resultsSize: number;
|
|
}) {
|
|
const allFailed: boolean = props.fails === props.total;
|
|
const icon: Icons = allFailed ? Icons.WARNING : Icons.EYE_SLASH;
|
|
|
|
return (
|
|
<div className="my-24 flex flex-col items-center justify-center space-y-3 text-center">
|
|
<IconPatch
|
|
icon={icon}
|
|
className={`text-xl ${allFailed ? "text-red-400" : "text-bink-600"}`}
|
|
/>
|
|
|
|
{/* standard suffix */}
|
|
{!allFailed ? (
|
|
<div>
|
|
{props.fails > 0 ? (
|
|
<p className="text-red-400">
|
|
{props.fails}/{props.total} providers failed!
|
|
</p>
|
|
) : null}
|
|
{props.resultsSize > 0 ? (
|
|
<p>That's all we have!</p>
|
|
) : (
|
|
<p>We couldn't find anything!</p>
|
|
)}
|
|
</div>
|
|
) : null}
|
|
|
|
{/* Error result */}
|
|
{allFailed ? (
|
|
<div>
|
|
<p>All providers have failed!</p>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function SearchResultsView({
|
|
searchQuery,
|
|
clear,
|
|
}: {
|
|
searchQuery: MWQuery;
|
|
clear: () => void;
|
|
}) {
|
|
const [results, setResults] = useState<MWMassProviderOutput | undefined>();
|
|
const [runSearchQuery, loading, error, success] = useLoading(
|
|
(query: MWQuery) => SearchProviders(query)
|
|
);
|
|
|
|
useEffect(() => {
|
|
async function runSearch(query: MWQuery) {
|
|
const searchResults = await runSearchQuery(query);
|
|
if (!searchResults) return;
|
|
setResults(searchResults);
|
|
}
|
|
|
|
if (searchQuery.searchQuery !== "") runSearch(searchQuery);
|
|
}, [searchQuery, runSearchQuery]);
|
|
|
|
return (
|
|
<div>
|
|
{/* results */}
|
|
{success && results?.results.length ? (
|
|
<SectionHeading
|
|
title="Search results"
|
|
icon={Icons.SEARCH}
|
|
linkText="Back to home"
|
|
onClick={() => clear()}
|
|
>
|
|
{results.results.map((v) => (
|
|
<WatchedMediaCard
|
|
key={[v.mediaId, v.providerId].join("|")}
|
|
media={v}
|
|
/>
|
|
))}
|
|
</SectionHeading>
|
|
) : null}
|
|
|
|
{/* search suffix */}
|
|
{success && results ? (
|
|
<SearchSuffix
|
|
resultsSize={results.results.length}
|
|
fails={results.stats.failed}
|
|
total={results.stats.total}
|
|
/>
|
|
) : null}
|
|
|
|
{/* error */}
|
|
{error ? <SearchSuffix resultsSize={0} fails={1} total={1} /> : null}
|
|
|
|
{/* Loading icon */}
|
|
{loading ? <SearchLoading /> : null}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function ExtraItems() {
|
|
const { getFilteredBookmarks } = useBookmarkContext();
|
|
const { getFilteredWatched } = useWatchedContext();
|
|
|
|
const bookmarks = getFilteredBookmarks();
|
|
|
|
const watchedItems = getFilteredWatched().filter(
|
|
(v) => !getIfBookmarkedFromPortable(bookmarks, v)
|
|
);
|
|
|
|
if (watchedItems.length === 0 && bookmarks.length === 0) return null;
|
|
|
|
return (
|
|
<div className="mb-16 mt-32">
|
|
{bookmarks.length > 0 ? (
|
|
<SectionHeading title="Bookmarks" icon={Icons.BOOKMARK}>
|
|
{bookmarks.map((v) => (
|
|
<WatchedMediaCard
|
|
key={[v.mediaId, v.providerId].join("|")}
|
|
media={v}
|
|
/>
|
|
))}
|
|
</SectionHeading>
|
|
) : null}
|
|
{watchedItems.length > 0 ? (
|
|
<SectionHeading title="Continue Watching" icon={Icons.CLOCK}>
|
|
{watchedItems.map((v) => (
|
|
<WatchedMediaCard
|
|
key={[v.mediaId, v.providerId].join("|")}
|
|
media={v}
|
|
series
|
|
/>
|
|
))}
|
|
</SectionHeading>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function SearchView() {
|
|
const [searching, setSearching] = useState<boolean>(false);
|
|
const [loading, setLoading] = useState<boolean>(false);
|
|
const [search, setSearch] = useSearchQuery();
|
|
|
|
const debouncedSearch = useDebounce<MWQuery>(search, 2000);
|
|
useEffect(() => {
|
|
setSearching(search.searchQuery !== "");
|
|
setLoading(search.searchQuery !== "");
|
|
}, [search]);
|
|
useEffect(() => {
|
|
setLoading(false);
|
|
}, [debouncedSearch]);
|
|
|
|
const resultView = useMemo(() => {
|
|
if (loading) return <SearchLoading />;
|
|
if (searching)
|
|
return (
|
|
<SearchResultsView
|
|
searchQuery={debouncedSearch}
|
|
clear={() => setSearch({ searchQuery: "" })}
|
|
/>
|
|
);
|
|
return <ExtraItems />;
|
|
}, [loading, searching, debouncedSearch, setSearch]);
|
|
|
|
return (
|
|
<>
|
|
<Navigation />
|
|
<ThinContainer>
|
|
{/* input section */}
|
|
<div className="mt-44 space-y-16 text-center">
|
|
<div className="space-y-4">
|
|
<Tagline>Because watching legally is boring</Tagline>
|
|
<Title>What movie do you want to watch?</Title>
|
|
</div>
|
|
<SearchBarInput
|
|
onChange={setSearch}
|
|
value={search}
|
|
placeholder="What movie do you want to watch?"
|
|
/>
|
|
</div>
|
|
|
|
{/* results view */}
|
|
{resultView}
|
|
</ThinContainer>
|
|
</>
|
|
);
|
|
}
|