|
@@ -13,6 +13,8 @@ interface SearchResultsProps {
|
|
|
isAdding: boolean;
|
|
isAdding: boolean;
|
|
|
addingTmdbId: number | null;
|
|
addingTmdbId: number | null;
|
|
|
onAdd: (tmdbId: number) => void;
|
|
onAdd: (tmdbId: number) => void;
|
|
|
|
|
+ isError?: boolean;
|
|
|
|
|
+ hasData?: boolean;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
export function SearchResults({
|
|
export function SearchResults({
|
|
@@ -22,6 +24,8 @@ export function SearchResults({
|
|
|
isAdding,
|
|
isAdding,
|
|
|
addingTmdbId,
|
|
addingTmdbId,
|
|
|
onAdd,
|
|
onAdd,
|
|
|
|
|
+ isError = false,
|
|
|
|
|
+ hasData = true,
|
|
|
}: SearchResultsProps) {
|
|
}: SearchResultsProps) {
|
|
|
if (!query || query.length < 2) return null;
|
|
if (!query || query.length < 2) return null;
|
|
|
|
|
|
|
@@ -29,9 +33,7 @@ export function SearchResults({
|
|
|
|
|
|
|
|
// Filter group movies that match the search query
|
|
// Filter group movies that match the search query
|
|
|
const lowerQuery = query.toLowerCase();
|
|
const lowerQuery = query.toLowerCase();
|
|
|
- const matchingGroupMovies = groupMovies.filter((m) =>
|
|
|
|
|
- m.title.toLowerCase().includes(lowerQuery),
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ const matchingGroupMovies = groupMovies.filter((m) => m.title.toLowerCase().includes(lowerQuery));
|
|
|
|
|
|
|
|
// Filter TMDB results: exclude movies already in the group
|
|
// Filter TMDB results: exclude movies already in the group
|
|
|
const newResults = tmdbResults.filter((m) => !groupTmdbIds.has(m.id));
|
|
const newResults = tmdbResults.filter((m) => !groupTmdbIds.has(m.id));
|
|
@@ -39,10 +41,28 @@ export function SearchResults({
|
|
|
const hasInList = matchingGroupMovies.length > 0;
|
|
const hasInList = matchingGroupMovies.length > 0;
|
|
|
const hasNew = newResults.length > 0;
|
|
const hasNew = newResults.length > 0;
|
|
|
|
|
|
|
|
- if (!hasInList && !hasNew) {
|
|
|
|
|
|
|
+ // Distinguish a network/fetch error (e.g. offline) from a genuine zero-hit
|
|
|
|
|
+ // TMDB response. Showing "No results" on a transport failure is misleading
|
|
|
|
|
+ // — the user thinks the movie doesn't exist when really the request never
|
|
|
|
|
+ // reached TMDB.
|
|
|
|
|
+ if (isError && !hasInList) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <p role="alert" aria-live="polite" className="py-4 text-center text-sm text-red-500">
|
|
|
|
|
+ Couldn't reach search — check your connection.
|
|
|
|
|
+ </p>
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!hasInList && !hasNew && hasData) {
|
|
|
return <p className="py-4 text-center text-sm text-gray-500">No results found.</p>;
|
|
return <p className="py-4 text-center text-sm text-gray-500">No results found.</p>;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ if (!hasInList && !hasNew) {
|
|
|
|
|
+ // Still loading first response and no local matches — render nothing
|
|
|
|
|
+ // rather than a misleading empty-state.
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
return (
|
|
return (
|
|
|
<div className="space-y-4" aria-live="polite">
|
|
<div className="space-y-4" aria-live="polite">
|
|
|
{hasInList && (
|
|
{hasInList && (
|
|
@@ -64,9 +84,7 @@ export function SearchResults({
|
|
|
</section>
|
|
</section>
|
|
|
)}
|
|
)}
|
|
|
|
|
|
|
|
- {hasInList && hasNew && (
|
|
|
|
|
- <hr className="border-gray-200 dark:border-gray-700" />
|
|
|
|
|
- )}
|
|
|
|
|
|
|
+ {hasInList && hasNew && <hr className="border-gray-200 dark:border-gray-700" />}
|
|
|
|
|
|
|
|
{hasNew && (
|
|
{hasNew && (
|
|
|
<section>
|
|
<section>
|