feat: discover overhaul (filters!) (#3232)

This commit is contained in:
Ryan Cohen
2023-01-13 16:54:35 +09:00
committed by GitHub
parent b5157010c4
commit dd00e48f59
26 changed files with 1445 additions and 179 deletions

View File

@@ -1,51 +0,0 @@
import Header from '@app/components/Common/Header';
import ListView from '@app/components/Common/ListView';
import PageTitle from '@app/components/Common/PageTitle';
import useDiscover from '@app/hooks/useDiscover';
import Error from '@app/pages/_error';
import type { MovieResult } from '@server/models/Search';
import { defineMessages, useIntl } from 'react-intl';
const messages = defineMessages({
discovermovies: 'Popular Movies',
});
const DiscoverMovies = () => {
const intl = useIntl();
const {
isLoadingInitialData,
isEmpty,
isLoadingMore,
isReachingEnd,
titles,
fetchMore,
error,
} = useDiscover<MovieResult>('/api/v1/discover/movies');
if (error) {
return <Error statusCode={500} />;
}
const title = intl.formatMessage(messages.discovermovies);
return (
<>
<PageTitle title={title} />
<div className="mt-1 mb-5">
<Header>{title}</Header>
</div>
<ListView
items={titles}
isEmpty={isEmpty}
isLoading={
isLoadingInitialData || (isLoadingMore && (titles?.length ?? 0) > 0)
}
isReachingEnd={isReachingEnd}
onScrollBottom={fetchMore}
/>
</>
);
};
export default DiscoverMovies;

View File

@@ -0,0 +1,147 @@
import Button from '@app/components/Common/Button';
import Header from '@app/components/Common/Header';
import ListView from '@app/components/Common/ListView';
import PageTitle from '@app/components/Common/PageTitle';
import type { FilterOptions } from '@app/components/Discover/constants';
import {
countActiveFilters,
prepareFilterValues,
} from '@app/components/Discover/constants';
import FilterSlideover from '@app/components/Discover/FilterSlideover';
import useDiscover from '@app/hooks/useDiscover';
import { useUpdateQueryParams } from '@app/hooks/useUpdateQueryParams';
import Error from '@app/pages/_error';
import { BarsArrowDownIcon, FunnelIcon } from '@heroicons/react/24/solid';
import type { SortOptions as TMDBSortOptions } from '@server/api/themoviedb';
import type { MovieResult } from '@server/models/Search';
import { useRouter } from 'next/router';
import { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
const messages = defineMessages({
discovermovies: 'Movies',
activefilters:
'{count, plural, one {# Active Filter} other {# Active Filters}}',
sortPopularityAsc: 'Popularity Ascending',
sortPopularityDesc: 'Popularity Descending',
sortReleaseDateAsc: 'Release Date Ascending',
sortReleaseDateDesc: 'Release Date Descending',
sortTmdbRatingAsc: 'TMDB Rating Ascending',
sortTmdbRatingDesc: 'TMDB Rating Descending',
sortTitleAsc: 'Title (A-Z) Ascending',
sortTitleDesc: 'Title (Z-A) Descending',
});
const SortOptions: Record<string, TMDBSortOptions> = {
PopularityAsc: 'popularity.asc',
PopularityDesc: 'popularity.desc',
ReleaseDateAsc: 'release_date.asc',
ReleaseDateDesc: 'release_date.desc',
TmdbRatingAsc: 'vote_average.asc',
TmdbRatingDesc: 'vote_average.desc',
TitleAsc: 'original_title.asc',
TitleDesc: 'original_title.desc',
} as const;
const DiscoverMovies = () => {
const intl = useIntl();
const router = useRouter();
const updateQueryParams = useUpdateQueryParams({});
const preparedFilters = prepareFilterValues(router.query);
const {
isLoadingInitialData,
isEmpty,
isLoadingMore,
isReachingEnd,
titles,
fetchMore,
error,
} = useDiscover<MovieResult, unknown, FilterOptions>(
'/api/v1/discover/movies',
preparedFilters
);
const [showFilters, setShowFilters] = useState(false);
if (error) {
return <Error statusCode={500} />;
}
const title = intl.formatMessage(messages.discovermovies);
return (
<>
<PageTitle title={title} />
<div className="mb-4 flex flex-col justify-between lg:flex-row lg:items-end">
<Header>{title}</Header>
<div className="mt-2 flex flex-grow flex-col sm:flex-row lg:flex-grow-0">
<div className="mb-2 flex flex-grow sm:mb-0 sm:mr-2 lg:flex-grow-0">
<span className="inline-flex cursor-default items-center rounded-l-md border border-r-0 border-gray-500 bg-gray-800 px-3 text-gray-100 sm:text-sm">
<BarsArrowDownIcon className="h-6 w-6" />
</span>
<select
id="sortBy"
name="sortBy"
className="rounded-r-only"
value={preparedFilters.sortBy}
onChange={(e) => updateQueryParams('sortBy', e.target.value)}
>
<option value={SortOptions.PopularityDesc}>
{intl.formatMessage(messages.sortPopularityDesc)}
</option>
<option value={SortOptions.PopularityAsc}>
{intl.formatMessage(messages.sortPopularityAsc)}
</option>
<option value={SortOptions.ReleaseDateDesc}>
{intl.formatMessage(messages.sortReleaseDateDesc)}
</option>
<option value={SortOptions.ReleaseDateAsc}>
{intl.formatMessage(messages.sortReleaseDateAsc)}
</option>
<option value={SortOptions.TmdbRatingDesc}>
{intl.formatMessage(messages.sortTmdbRatingDesc)}
</option>
<option value={SortOptions.TmdbRatingAsc}>
{intl.formatMessage(messages.sortTmdbRatingAsc)}
</option>
<option value={SortOptions.TitleAsc}>
{intl.formatMessage(messages.sortTitleAsc)}
</option>
<option value={SortOptions.TitleDesc}>
{intl.formatMessage(messages.sortTitleDesc)}
</option>
</select>
</div>
<FilterSlideover
type="movie"
currentFilters={preparedFilters}
onClose={() => setShowFilters(false)}
show={showFilters}
/>
<div className="mb-2 flex flex-grow sm:mb-0 lg:flex-grow-0">
<Button onClick={() => setShowFilters(true)} className="w-full">
<FunnelIcon />
<span>
{intl.formatMessage(messages.activefilters, {
count: countActiveFilters(preparedFilters),
})}
</span>
</Button>
</div>
</div>
</div>
<ListView
items={titles}
isEmpty={isEmpty}
isLoading={
isLoadingInitialData || (isLoadingMore && (titles?.length ?? 0) > 0)
}
isReachingEnd={isReachingEnd}
onScrollBottom={fetchMore}
/>
</>
);
};
export default DiscoverMovies;

View File

@@ -1,51 +0,0 @@
import Header from '@app/components/Common/Header';
import ListView from '@app/components/Common/ListView';
import PageTitle from '@app/components/Common/PageTitle';
import useDiscover from '@app/hooks/useDiscover';
import Error from '@app/pages/_error';
import type { TvResult } from '@server/models/Search';
import { defineMessages, useIntl } from 'react-intl';
const messages = defineMessages({
discovertv: 'Popular Series',
});
const DiscoverTv = () => {
const intl = useIntl();
const {
isLoadingInitialData,
isEmpty,
isLoadingMore,
isReachingEnd,
titles,
fetchMore,
error,
} = useDiscover<TvResult>('/api/v1/discover/tv');
if (error) {
return <Error statusCode={500} />;
}
const title = intl.formatMessage(messages.discovertv);
return (
<>
<PageTitle title={title} />
<div className="mt-1 mb-5">
<Header>{title}</Header>
</div>
<ListView
items={titles}
isEmpty={isEmpty}
isReachingEnd={isReachingEnd}
isLoading={
isLoadingInitialData || (isLoadingMore && (titles?.length ?? 0) > 0)
}
onScrollBottom={fetchMore}
/>
</>
);
};
export default DiscoverTv;

View File

@@ -0,0 +1,145 @@
import Button from '@app/components/Common/Button';
import Header from '@app/components/Common/Header';
import ListView from '@app/components/Common/ListView';
import PageTitle from '@app/components/Common/PageTitle';
import type { FilterOptions } from '@app/components/Discover/constants';
import {
countActiveFilters,
prepareFilterValues,
} from '@app/components/Discover/constants';
import FilterSlideover from '@app/components/Discover/FilterSlideover';
import useDiscover from '@app/hooks/useDiscover';
import { useUpdateQueryParams } from '@app/hooks/useUpdateQueryParams';
import Error from '@app/pages/_error';
import { BarsArrowDownIcon, FunnelIcon } from '@heroicons/react/24/solid';
import type { SortOptions as TMDBSortOptions } from '@server/api/themoviedb';
import type { TvResult } from '@server/models/Search';
import { useRouter } from 'next/router';
import { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
const messages = defineMessages({
discovertv: 'Series',
activefilters:
'{count, plural, one {# Active Filter} other {# Active Filters}}',
sortPopularityAsc: 'Popularity Ascending',
sortPopularityDesc: 'Popularity Descending',
sortFirstAirDateAsc: 'First Air Date Ascending',
sortFirstAirDateDesc: 'First Air Date Descending',
sortTmdbRatingAsc: 'TMDB Rating Ascending',
sortTmdbRatingDesc: 'TMDB Rating Descending',
sortTitleAsc: 'Title (A-Z) Ascending',
sortTitleDesc: 'Title (Z-A) Descending',
});
const SortOptions: Record<string, TMDBSortOptions> = {
PopularityAsc: 'popularity.asc',
PopularityDesc: 'popularity.desc',
FirstAirDateAsc: 'first_air_date.asc',
FirstAirDateDesc: 'first_air_date.desc',
TmdbRatingAsc: 'vote_average.asc',
TmdbRatingDesc: 'vote_average.desc',
TitleAsc: 'original_title.asc',
TitleDesc: 'original_title.desc',
} as const;
const DiscoverTv = () => {
const intl = useIntl();
const router = useRouter();
const [showFilters, setShowFilters] = useState(false);
const preparedFilters = prepareFilterValues(router.query);
const updateQueryParams = useUpdateQueryParams({});
const {
isLoadingInitialData,
isEmpty,
isLoadingMore,
isReachingEnd,
titles,
fetchMore,
error,
} = useDiscover<TvResult, never, FilterOptions>('/api/v1/discover/tv', {
...preparedFilters,
});
if (error) {
return <Error statusCode={500} />;
}
const title = intl.formatMessage(messages.discovertv);
return (
<>
<PageTitle title={title} />
<div className="mb-4 flex flex-col justify-between lg:flex-row lg:items-end">
<Header>{title}</Header>
<div className="mt-2 flex flex-grow flex-col sm:flex-row lg:flex-grow-0">
<div className="mb-2 flex flex-grow sm:mb-0 sm:mr-2 lg:flex-grow-0">
<span className="inline-flex cursor-default items-center rounded-l-md border border-r-0 border-gray-500 bg-gray-800 px-3 text-gray-100 sm:text-sm">
<BarsArrowDownIcon className="h-6 w-6" />
</span>
<select
id="sortBy"
name="sortBy"
className="rounded-r-only"
value={preparedFilters.sortBy}
onChange={(e) => updateQueryParams('sortBy', e.target.value)}
>
<option value={SortOptions.PopularityDesc}>
{intl.formatMessage(messages.sortPopularityDesc)}
</option>
<option value={SortOptions.PopularityAsc}>
{intl.formatMessage(messages.sortPopularityAsc)}
</option>
<option value={SortOptions.ReleaseDateDesc}>
{intl.formatMessage(messages.sortFirstAirDateDesc)}
</option>
<option value={SortOptions.ReleaseDateAsc}>
{intl.formatMessage(messages.sortFirstAirDateAsc)}
</option>
<option value={SortOptions.TmdbRatingDesc}>
{intl.formatMessage(messages.sortTmdbRatingDesc)}
</option>
<option value={SortOptions.TmdbRatingAsc}>
{intl.formatMessage(messages.sortTmdbRatingAsc)}
</option>
<option value={SortOptions.TitleAsc}>
{intl.formatMessage(messages.sortTitleAsc)}
</option>
<option value={SortOptions.TitleDesc}>
{intl.formatMessage(messages.sortTitleDesc)}
</option>
</select>
</div>
<FilterSlideover
type="tv"
currentFilters={preparedFilters}
onClose={() => setShowFilters(false)}
show={showFilters}
/>
<div className="mb-2 flex flex-grow sm:mb-0 lg:flex-grow-0">
<Button onClick={() => setShowFilters(true)} className="w-full">
<FunnelIcon />
<span>
{intl.formatMessage(messages.activefilters, {
count: countActiveFilters(preparedFilters),
})}
</span>
</Button>
</div>
</div>
</div>
<ListView
items={titles}
isEmpty={isEmpty}
isReachingEnd={isReachingEnd}
isLoading={
isLoadingInitialData || (isLoadingMore && (titles?.length ?? 0) > 0)
}
onScrollBottom={fetchMore}
/>
</>
);
};
export default DiscoverTv;

View File

@@ -0,0 +1,271 @@
import Button from '@app/components/Common/Button';
import MultiRangeSlider from '@app/components/Common/MultiRangeSlider';
import SlideOver from '@app/components/Common/SlideOver';
import type { FilterOptions } from '@app/components/Discover/constants';
import { countActiveFilters } from '@app/components/Discover/constants';
import LanguageSelector from '@app/components/LanguageSelector';
import {
CompanySelector,
GenreSelector,
KeywordSelector,
} from '@app/components/Selector';
import useSettings from '@app/hooks/useSettings';
import {
useBatchUpdateQueryParams,
useUpdateQueryParams,
} from '@app/hooks/useUpdateQueryParams';
import { XCircleIcon } from '@heroicons/react/24/outline';
import { defineMessages, useIntl } from 'react-intl';
import Datepicker from 'react-tailwindcss-datepicker-sct';
const messages = defineMessages({
filters: 'Filters',
activefilters:
'{count, plural, one {# Active Filter} other {# Active Filters}}',
releaseDate: 'Release Date',
firstAirDate: 'First Air Date',
from: 'From',
to: 'To',
studio: 'Studio',
genres: 'Genres',
keywords: 'Keywords',
originalLanguage: 'Original Language',
runtimeText: '{minValue}-{maxValue} minute runtime',
ratingText: 'Ratings between {minValue} and {maxValue}',
clearfilters: 'Clear Active Filters',
tmdbuserscore: 'TMDB User Score',
runtime: 'Runtime',
});
type FilterSlideoverProps = {
show: boolean;
onClose: () => void;
type: 'movie' | 'tv';
currentFilters: FilterOptions;
};
const FilterSlideover = ({
show,
onClose,
type,
currentFilters,
}: FilterSlideoverProps) => {
const intl = useIntl();
const { currentSettings } = useSettings();
const updateQueryParams = useUpdateQueryParams({});
const batchUpdateQueryParams = useBatchUpdateQueryParams({});
const dateGte =
type === 'movie' ? 'primaryReleaseDateGte' : 'firstAirDateGte';
const dateLte =
type === 'movie' ? 'primaryReleaseDateLte' : 'firstAirDateLte';
return (
<SlideOver
show={show}
title={intl.formatMessage(messages.filters)}
subText={intl.formatMessage(messages.activefilters, {
count: countActiveFilters(currentFilters),
})}
onClose={() => onClose()}
>
<div className="flex flex-col space-y-4">
<div>
<div className="mb-2 text-lg font-semibold">
{intl.formatMessage(
type === 'movie' ? messages.releaseDate : messages.firstAirDate
)}
</div>
<div className="relative z-40 flex space-x-2">
<div className="flex flex-col">
<div className="mb-2">{intl.formatMessage(messages.from)}</div>
<Datepicker
primaryColor="indigo"
value={{
startDate: currentFilters[dateGte] ?? null,
endDate: currentFilters[dateGte] ?? null,
}}
onChange={(value) => {
updateQueryParams(
dateGte,
value?.startDate ? (value.startDate as string) : undefined
);
}}
inputName="fromdate"
useRange={false}
asSingle
containerClassName="datepicker-wrapper"
inputClassName="pr-1 sm:pr-4"
/>
</div>
<div className="flex flex-col">
<div className="mb-2">{intl.formatMessage(messages.to)}</div>
<Datepicker
primaryColor="indigo"
value={{
startDate: currentFilters[dateLte] ?? null,
endDate: currentFilters[dateLte] ?? null,
}}
onChange={(value) => {
updateQueryParams(
dateLte,
value?.startDate ? (value.startDate as string) : undefined
);
}}
inputName="todate"
useRange={false}
asSingle
containerClassName="datepicker-wrapper"
inputClassName="pr-1 sm:pr-4"
/>
</div>
</div>
</div>
{type === 'movie' && (
<>
<span className="text-lg font-semibold">
{intl.formatMessage(messages.studio)}
</span>
<CompanySelector
defaultValue={currentFilters.studio}
onChange={(value) => {
updateQueryParams('studio', value?.value.toString());
}}
/>
</>
)}
<span className="text-lg font-semibold">
{intl.formatMessage(messages.genres)}
</span>
<GenreSelector
type={type}
defaultValue={currentFilters.genre}
isMulti
onChange={(value) => {
updateQueryParams('genre', value?.map((v) => v.value).join(','));
}}
/>
<span className="text-lg font-semibold">
{intl.formatMessage(messages.keywords)}
</span>
<KeywordSelector
defaultValue={currentFilters.keywords}
isMulti
onChange={(value) => {
updateQueryParams('keywords', value?.map((v) => v.value).join(','));
}}
/>
<span className="text-lg font-semibold">
{intl.formatMessage(messages.originalLanguage)}
</span>
<LanguageSelector
value={currentFilters.language}
serverValue={currentSettings.originalLanguage}
isUserSettings
setFieldValue={(_key, value) => {
updateQueryParams('language', value);
}}
/>
<span className="text-lg font-semibold">
{intl.formatMessage(messages.runtime)}
</span>
<div className="relative z-0">
<MultiRangeSlider
min={0}
max={400}
onUpdateMin={(min) => {
updateQueryParams(
'withRuntimeGte',
min !== 0 && Number(currentFilters.withRuntimeLte) !== 400
? min.toString()
: undefined
);
}}
onUpdateMax={(max) => {
updateQueryParams(
'withRuntimeLte',
max !== 400 && Number(currentFilters.withRuntimeGte) !== 0
? max.toString()
: undefined
);
}}
defaultMaxValue={
currentFilters.withRuntimeLte
? Number(currentFilters.withRuntimeLte)
: undefined
}
defaultMinValue={
currentFilters.withRuntimeGte
? Number(currentFilters.withRuntimeGte)
: undefined
}
subText={intl.formatMessage(messages.runtimeText, {
minValue: currentFilters.withRuntimeGte ?? 0,
maxValue: currentFilters.withRuntimeLte ?? 400,
})}
/>
</div>
<span className="text-lg font-semibold">
{intl.formatMessage(messages.tmdbuserscore)}
</span>
<div className="relative z-0">
<MultiRangeSlider
min={1}
max={10}
defaultMaxValue={
currentFilters.voteAverageLte
? Number(currentFilters.voteAverageLte)
: undefined
}
defaultMinValue={
currentFilters.voteAverageGte
? Number(currentFilters.voteAverageGte)
: undefined
}
onUpdateMin={(min) => {
updateQueryParams(
'voteAverageGte',
min !== 1 && Number(currentFilters.voteAverageLte) !== 10
? min.toString()
: undefined
);
}}
onUpdateMax={(max) => {
updateQueryParams(
'voteAverageLte',
max !== 10 && Number(currentFilters.voteAverageGte) !== 1
? max.toString()
: undefined
);
}}
subText={intl.formatMessage(messages.ratingText, {
minValue: currentFilters.voteAverageGte ?? 1,
maxValue: currentFilters.voteAverageLte ?? 10,
})}
/>
</div>
<div className="pt-4">
<Button
className="w-full"
disabled={Object.keys(currentFilters).length === 0}
onClick={() => {
const copyCurrent = Object.assign({}, currentFilters);
(
Object.keys(copyCurrent) as (keyof typeof currentFilters)[]
).forEach((k) => {
copyCurrent[k] = undefined;
});
batchUpdateQueryParams(copyCurrent);
onClose();
}}
>
<XCircleIcon />
<span>{intl.formatMessage(messages.clearfilters)}</span>
</Button>
</div>
</div>
</SlideOver>
);
};
export default FilterSlideover;

View File

@@ -43,7 +43,7 @@ const MovieGenreSlider = () => {
image={`https://image.tmdb.org/t/p/w1280_filter(duotone,${
genreColorMap[genre.id] ?? genreColorMap[0]
})${genre.backdrops[4]}`}
url={`/discover/movies/genre/${genre.id}`}
url={`/discover/movies?genre=${genre.id}`}
/>
))}
placeholder={<GenreCard.Placeholder />}

View File

@@ -43,7 +43,7 @@ const TvGenreSlider = () => {
image={`https://image.tmdb.org/t/p/w1280_filter(duotone,${
genreColorMap[genre.id] ?? genreColorMap[0]
})${genre.backdrops[4]}`}
url={`/discover/tv/genre/${genre.id}`}
url={`/discover/tv?genre=${genre.id}`}
/>
))}
placeholder={<GenreCard.Placeholder />}

View File

@@ -1,4 +1,6 @@
import type { ParsedUrlQuery } from 'querystring';
import { defineMessages } from 'react-intl';
import { z } from 'zod';
type AvailableColors =
| 'black'
@@ -85,3 +87,104 @@ export const sliderTitles = defineMessages({
tmdbstudio: 'TMDB Studio',
tmdbsearch: 'TMDB Search',
});
export const QueryFilterOptions = z.object({
sortBy: z.string().optional(),
primaryReleaseDateGte: z.string().optional(),
primaryReleaseDateLte: z.string().optional(),
firstAirDateGte: z.string().optional(),
firstAirDateLte: z.string().optional(),
studio: z.string().optional(),
genre: z.string().optional(),
keywords: z.string().optional(),
language: z.string().optional(),
withRuntimeGte: z.string().optional(),
withRuntimeLte: z.string().optional(),
voteAverageGte: z.string().optional(),
voteAverageLte: z.string().optional(),
});
export type FilterOptions = z.infer<typeof QueryFilterOptions>;
export const prepareFilterValues = (
inputValues: ParsedUrlQuery
): FilterOptions => {
const filterValues: FilterOptions = {};
const values = QueryFilterOptions.parse(inputValues);
if (values.sortBy) {
filterValues.sortBy = values.sortBy;
}
if (values.primaryReleaseDateGte) {
filterValues.primaryReleaseDateGte = values.primaryReleaseDateGte;
}
if (values.primaryReleaseDateLte) {
filterValues.primaryReleaseDateLte = values.primaryReleaseDateLte;
}
if (values.firstAirDateGte) {
filterValues.firstAirDateGte = values.firstAirDateGte;
}
if (values.firstAirDateLte) {
filterValues.firstAirDateLte = values.firstAirDateLte;
}
if (values.studio) {
filterValues.studio = values.studio;
}
if (values.genre) {
filterValues.genre = values.genre;
}
if (values.keywords) {
filterValues.keywords = values.keywords;
}
if (values.language) {
filterValues.language = values.language;
}
if (values.withRuntimeGte) {
filterValues.withRuntimeGte = values.withRuntimeGte;
}
if (values.withRuntimeLte) {
filterValues.withRuntimeLte = values.withRuntimeLte;
}
if (values.voteAverageGte) {
filterValues.voteAverageGte = values.voteAverageGte;
}
if (values.voteAverageLte) {
filterValues.voteAverageLte = values.voteAverageLte;
}
return filterValues;
};
export const countActiveFilters = (filterValues: FilterOptions): number => {
let totalCount = 0;
const clonedFilters = Object.assign({}, filterValues);
if (clonedFilters.voteAverageGte || filterValues.voteAverageLte) {
totalCount += 1;
delete clonedFilters.voteAverageGte;
delete clonedFilters.voteAverageLte;
}
if (clonedFilters.withRuntimeGte || filterValues.withRuntimeLte) {
totalCount += 1;
delete clonedFilters.withRuntimeGte;
delete clonedFilters.withRuntimeLte;
}
totalCount += Object.keys(clonedFilters).length;
return totalCount;
};

View File

@@ -109,6 +109,12 @@ const Discover = () => {
}
};
const now = new Date();
const offset = now.getTimezoneOffset();
const upcomingDate = new Date(now.getTime() - offset * 60 * 1000)
.toISOString()
.split('T')[0];
if (!discoverData && !discoverError) {
return <LoadingSpinner />;
}
@@ -240,8 +246,9 @@ const Discover = () => {
<MediaSlider
sliderKey="upcoming"
title={intl.formatMessage(sliderTitles.upcoming)}
linkUrl="/discover/movies/upcoming"
url="/api/v1/discover/movies/upcoming"
linkUrl={`/discover/movies?primaryReleaseDateGte=${upcomingDate}`}
url="/api/v1/discover/movies"
extraParams={`primaryReleaseDateGte=${upcomingDate}`}
/>
);
break;
@@ -266,8 +273,9 @@ const Discover = () => {
<MediaSlider
sliderKey="upcoming-tv"
title={intl.formatMessage(sliderTitles.upcomingtv)}
url="/api/v1/discover/tv/upcoming"
linkUrl="/discover/tv/upcoming"
linkUrl={`/discover/tv?firstAirDateGte=${upcomingDate}`}
url="/api/v1/discover/tv"
extraParams={`firstAirDateGte=${upcomingDate}`}
/>
);
break;
@@ -285,7 +293,7 @@ const Discover = () => {
? `keywords=${encodeURIExtraParams(slider.data)}`
: ''
}
linkUrl={`/discover/movies/keyword?keywords=${slider.data}`}
linkUrl={`/discover/movies?keywords=${slider.data}`}
/>
);
break;
@@ -300,7 +308,7 @@ const Discover = () => {
? `keywords=${encodeURIExtraParams(slider.data)}`
: ''
}
linkUrl={`/discover/tv/keyword?keywords=${slider.data}`}
linkUrl={`/discover/tv?keywords=${slider.data}`}
/>
);
break;
@@ -309,8 +317,9 @@ const Discover = () => {
<MediaSlider
sliderKey={`custom-slider-${slider.id}`}
title={slider.title ?? ''}
url={`/api/v1/discover/movies/genre/${slider.data}`}
linkUrl={`/discover/movies/genre/${slider.data}`}
url={`/api/v1/discover/movies`}
extraParams={`genre=${slider.data}`}
linkUrl={`/discover/movies?genre=${slider.data}`}
/>
);
break;
@@ -319,8 +328,9 @@ const Discover = () => {
<MediaSlider
sliderKey={`custom-slider-${slider.id}`}
title={slider.title ?? ''}
url={`/api/v1/discover/tv/genre/${slider.data}`}
linkUrl={`/discover/tv/genre/${slider.data}`}
url={`/api/v1/discover/tv`}
extraParams={`genre=${slider.data}`}
linkUrl={`/discover/tv?genre=${slider.data}`}
/>
);
break;