feat: add genre/studio/network view to Discover results (#1067)
* feat: add genres view to movie/series Discover results * feat: add studio/network view to movie/series Discover results * fix: remove with_release_type filter, since it is removing valid/desired results
This commit is contained in:
@@ -3223,6 +3223,16 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
example: en
|
example: en
|
||||||
|
- in: query
|
||||||
|
name: genre
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
example: 10751
|
||||||
|
- in: query
|
||||||
|
name: studio
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
example: 2
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Results
|
description: Results
|
||||||
@@ -3301,6 +3311,16 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
example: en
|
example: en
|
||||||
|
- in: query
|
||||||
|
name: genre
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
example: 18
|
||||||
|
- in: query
|
||||||
|
name: network
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
example: 1
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Results
|
description: Results
|
||||||
@@ -4326,14 +4346,16 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: array
|
||||||
properties:
|
items:
|
||||||
iso_3166_1:
|
type: object
|
||||||
type: string
|
properties:
|
||||||
example: US
|
iso_3166_1:
|
||||||
english_name:
|
type: string
|
||||||
type: string
|
example: US
|
||||||
example: United States of America
|
english_name:
|
||||||
|
type: string
|
||||||
|
example: United States of America
|
||||||
/languages:
|
/languages:
|
||||||
get:
|
get:
|
||||||
summary: Languages supported by TMDb
|
summary: Languages supported by TMDb
|
||||||
@@ -4346,17 +4368,103 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: array
|
||||||
properties:
|
items:
|
||||||
iso_639_1:
|
type: object
|
||||||
type: string
|
properties:
|
||||||
example: en
|
iso_639_1:
|
||||||
english_name:
|
type: string
|
||||||
type: string
|
example: en
|
||||||
example: English
|
english_name:
|
||||||
name:
|
type: string
|
||||||
type: string
|
example: English
|
||||||
example: English
|
name:
|
||||||
|
type: string
|
||||||
|
example: English
|
||||||
|
/studio/{studioId}:
|
||||||
|
get:
|
||||||
|
summary: Get movie studio details
|
||||||
|
description: Returns movie studio details in a JSON object.
|
||||||
|
tags:
|
||||||
|
- tmdb
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: studioId
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
example: 2
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Movie studio details
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ProductionCompany'
|
||||||
|
/network/{networkId}:
|
||||||
|
get:
|
||||||
|
summary: Get TV network details
|
||||||
|
description: Returns TV network details in a JSON object.
|
||||||
|
tags:
|
||||||
|
- tmdb
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: networkId
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
example: 1
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: TV network details
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ProductionCompany'
|
||||||
|
/genres/movie:
|
||||||
|
get:
|
||||||
|
summary: Get list of official TMDb movie genres
|
||||||
|
description: Returns a list of genres in a JSON array.
|
||||||
|
tags:
|
||||||
|
- tmdb
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Results
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: number
|
||||||
|
example: 10751
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
example: Family
|
||||||
|
/genres/tv:
|
||||||
|
get:
|
||||||
|
summary: Get list of official TMDb movie genres
|
||||||
|
description: Returns a list of genres in a JSON array.
|
||||||
|
tags:
|
||||||
|
- tmdb
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Results
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: number
|
||||||
|
example: 18
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
example: Drama
|
||||||
|
|
||||||
security:
|
security:
|
||||||
- cookieAuth: []
|
- cookieAuth: []
|
||||||
|
|||||||
@@ -3,9 +3,13 @@ import cacheManager from '../../lib/cache';
|
|||||||
import ExternalAPI from '../externalapi';
|
import ExternalAPI from '../externalapi';
|
||||||
import {
|
import {
|
||||||
TmdbCollection,
|
TmdbCollection,
|
||||||
|
TmdbStudio,
|
||||||
TmdbExternalIdResponse,
|
TmdbExternalIdResponse,
|
||||||
|
TmdbGenre,
|
||||||
|
TmdbGenresResult,
|
||||||
TmdbLanguage,
|
TmdbLanguage,
|
||||||
TmdbMovieDetails,
|
TmdbMovieDetails,
|
||||||
|
TmdbNetwork,
|
||||||
TmdbPersonCombinedCredits,
|
TmdbPersonCombinedCredits,
|
||||||
TmdbPersonDetail,
|
TmdbPersonDetail,
|
||||||
TmdbRegion,
|
TmdbRegion,
|
||||||
@@ -30,6 +34,8 @@ interface DiscoverMovieOptions {
|
|||||||
language?: string;
|
language?: string;
|
||||||
primaryReleaseDateGte?: string;
|
primaryReleaseDateGte?: string;
|
||||||
primaryReleaseDateLte?: string;
|
primaryReleaseDateLte?: string;
|
||||||
|
genre?: number;
|
||||||
|
studio?: number;
|
||||||
sortBy?:
|
sortBy?:
|
||||||
| 'popularity.asc'
|
| 'popularity.asc'
|
||||||
| 'popularity.desc'
|
| 'popularity.desc'
|
||||||
@@ -53,6 +59,8 @@ interface DiscoverTvOptions {
|
|||||||
firstAirDateGte?: string;
|
firstAirDateGte?: string;
|
||||||
firstAirDateLte?: string;
|
firstAirDateLte?: string;
|
||||||
includeEmptyReleaseDate?: boolean;
|
includeEmptyReleaseDate?: boolean;
|
||||||
|
genre?: number;
|
||||||
|
network?: number;
|
||||||
sortBy?:
|
sortBy?:
|
||||||
| 'popularity.asc'
|
| 'popularity.asc'
|
||||||
| 'popularity.desc'
|
| 'popularity.desc'
|
||||||
@@ -120,7 +128,7 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`[TMDB] Failed to fetch person details: ${e.message}`);
|
throw new Error(`[TMDb] Failed to fetch person details: ${e.message}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -142,7 +150,7 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`[TMDB] Failed to fetch person combined credits: ${e.message}`
|
`[TMDb] Failed to fetch person combined credits: ${e.message}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -168,7 +176,7 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`[TMDB] Failed to fetch movie details: ${e.message}`);
|
throw new Error(`[TMDb] Failed to fetch movie details: ${e.message}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -194,7 +202,7 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`[TMDB] Failed to fetch tv show details: ${e.message}`);
|
throw new Error(`[TMDb] Failed to fetch TV show details: ${e.message}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -220,7 +228,7 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`[TMDB] Failed to fetch tv show details: ${e.message}`);
|
throw new Error(`[TMDb] Failed to fetch TV show details: ${e.message}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -246,7 +254,7 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`[TMDB] Failed to fetch discover movies: ${e.message}`);
|
throw new Error(`[TMDb] Failed to fetch discover movies: ${e.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,7 +280,7 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`[TMDB] Failed to fetch discover movies: ${e.message}`);
|
throw new Error(`[TMDb] Failed to fetch discover movies: ${e.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,7 +306,7 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`[TMDB] Failed to fetch movies by keyword: ${e.message}`);
|
throw new Error(`[TMDb] Failed to fetch movies by keyword: ${e.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,7 +333,7 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`[TMDB] Failed to fetch tv recommendations: ${e.message}`
|
`[TMDb] Failed to fetch TV recommendations: ${e.message}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -349,7 +357,7 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`[TMDB] Failed to fetch tv similar: ${e.message}`);
|
throw new Error(`[TMDb] Failed to fetch TV similar: ${e.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,6 +368,8 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
language = 'en',
|
language = 'en',
|
||||||
primaryReleaseDateGte,
|
primaryReleaseDateGte,
|
||||||
primaryReleaseDateLte,
|
primaryReleaseDateLte,
|
||||||
|
genre,
|
||||||
|
studio,
|
||||||
}: DiscoverMovieOptions = {}): Promise<TmdbSearchMovieResponse> => {
|
}: DiscoverMovieOptions = {}): Promise<TmdbSearchMovieResponse> => {
|
||||||
try {
|
try {
|
||||||
const data = await this.get<TmdbSearchMovieResponse>('/discover/movie', {
|
const data = await this.get<TmdbSearchMovieResponse>('/discover/movie', {
|
||||||
@@ -368,17 +378,18 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
page,
|
page,
|
||||||
include_adult: includeAdult,
|
include_adult: includeAdult,
|
||||||
language,
|
language,
|
||||||
with_release_type: '3|2',
|
|
||||||
region: this.region,
|
region: this.region,
|
||||||
with_original_language: this.originalLanguage,
|
with_original_language: this.originalLanguage,
|
||||||
'primary_release_date.gte': primaryReleaseDateGte,
|
'primary_release_date.gte': primaryReleaseDateGte,
|
||||||
'primary_release_date.lte': primaryReleaseDateLte,
|
'primary_release_date.lte': primaryReleaseDateLte,
|
||||||
|
with_genres: genre,
|
||||||
|
with_companies: studio,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`[TMDB] Failed to fetch discover movies: ${e.message}`);
|
throw new Error(`[TMDb] Failed to fetch discover movies: ${e.message}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -389,6 +400,8 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
firstAirDateGte,
|
firstAirDateGte,
|
||||||
firstAirDateLte,
|
firstAirDateLte,
|
||||||
includeEmptyReleaseDate = false,
|
includeEmptyReleaseDate = false,
|
||||||
|
genre,
|
||||||
|
network,
|
||||||
}: DiscoverTvOptions = {}): Promise<TmdbSearchTvResponse> => {
|
}: DiscoverTvOptions = {}): Promise<TmdbSearchTvResponse> => {
|
||||||
try {
|
try {
|
||||||
const data = await this.get<TmdbSearchTvResponse>('/discover/tv', {
|
const data = await this.get<TmdbSearchTvResponse>('/discover/tv', {
|
||||||
@@ -401,12 +414,14 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
'first_air_date.lte': firstAirDateLte,
|
'first_air_date.lte': firstAirDateLte,
|
||||||
with_original_language: this.originalLanguage,
|
with_original_language: this.originalLanguage,
|
||||||
include_null_first_air_dates: includeEmptyReleaseDate,
|
include_null_first_air_dates: includeEmptyReleaseDate,
|
||||||
|
with_genres: genre,
|
||||||
|
with_networks: network,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`[TMDB] Failed to fetch discover tv: ${e.message}`);
|
throw new Error(`[TMDb] Failed to fetch discover TV: ${e.message}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -432,7 +447,7 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`[TMDB] Failed to fetch upcoming movies: ${e.message}`);
|
throw new Error(`[TMDb] Failed to fetch upcoming movies: ${e.message}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -459,7 +474,7 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`[TMDB] Failed to fetch all trending: ${e.message}`);
|
throw new Error(`[TMDb] Failed to fetch all trending: ${e.message}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -482,7 +497,7 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`[TMDB] Failed to fetch all trending: ${e.message}`);
|
throw new Error(`[TMDb] Failed to fetch all trending: ${e.message}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -505,7 +520,7 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`[TMDB] Failed to fetch all trending: ${e.message}`);
|
throw new Error(`[TMDb] Failed to fetch all trending: ${e.message}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -537,7 +552,7 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`[TMDB] Failed to find by external ID: ${e.message}`);
|
throw new Error(`[TMDb] Failed to find by external ID: ${e.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,11 +579,11 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'[TMDB] Failed to find a title with the provided IMDB id'
|
'[TMDb] Failed to find a title with the provided IMDB id'
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`[TMDB] Failed to get movie by external imdb ID: ${e.message}`
|
`[TMDb] Failed to get movie by external imdb ID: ${e.message}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -596,11 +611,11 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`[TMDB] Failed to find a TV show with the provided TVDB ID: ${tvdbId}`
|
`[TMDb] Failed to find a TV show with the provided TVDB ID: ${tvdbId}`
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`[TMDB] Failed to get TV show using the external TVDB ID: ${e.message}`
|
`[TMDb] Failed to get TV show using the external TVDB ID: ${e.message}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -624,7 +639,7 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`[TMDB] Failed to fetch collection: ${e.message}`);
|
throw new Error(`[TMDb] Failed to fetch collection: ${e.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -640,7 +655,7 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
|
|
||||||
return regions;
|
return regions;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`[TMDB] Failed to fetch countries: ${e.message}`);
|
throw new Error(`[TMDb] Failed to fetch countries: ${e.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,7 +671,59 @@ class TheMovieDb extends ExternalAPI {
|
|||||||
|
|
||||||
return languages;
|
return languages;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`[TMDB] Failed to fetch langauges: ${e.message}`);
|
throw new Error(`[TMDb] Failed to fetch langauges: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getStudio(studioId: number): Promise<TmdbStudio> {
|
||||||
|
try {
|
||||||
|
const data = await this.get<TmdbStudio>(`/company/${studioId}`);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`[TMDb] Failed to fetch movie studio: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getNetwork(networkId: number): Promise<TmdbNetwork> {
|
||||||
|
try {
|
||||||
|
const data = await this.get<TmdbNetwork>(`/network/${networkId}`);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`[TMDb] Failed to fetch TV network: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getMovieGenres(): Promise<TmdbGenre[]> {
|
||||||
|
try {
|
||||||
|
const data = await this.get<TmdbGenresResult>(
|
||||||
|
'/genre/movie/list',
|
||||||
|
{},
|
||||||
|
86400 // 24 hours
|
||||||
|
);
|
||||||
|
|
||||||
|
const movieGenres = sortBy(data.genres, 'name');
|
||||||
|
|
||||||
|
return movieGenres;
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`[TMDb] Failed to fetch movie genres: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getTvGenres(): Promise<TmdbGenre[]> {
|
||||||
|
try {
|
||||||
|
const data = await this.get<TmdbGenresResult>(
|
||||||
|
'/genre/tv/list',
|
||||||
|
{},
|
||||||
|
86400 // 24 hours
|
||||||
|
);
|
||||||
|
|
||||||
|
const tvGenres = sortBy(data.genres, 'name');
|
||||||
|
|
||||||
|
return tvGenres;
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`[TMDb] Failed to fetch TV genres: ${e.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -381,3 +381,32 @@ export interface TmdbLanguage {
|
|||||||
english_name: string;
|
english_name: string;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TmdbGenresResult {
|
||||||
|
genres: TmdbGenre[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TmdbGenre {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TmdbStudio {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
headquarters?: string;
|
||||||
|
homepage?: string;
|
||||||
|
logo_path?: string;
|
||||||
|
origin_country?: string;
|
||||||
|
parent_company?: TmdbStudio;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TmdbNetwork {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
headquarters?: string;
|
||||||
|
homepage?: string;
|
||||||
|
logo_path?: string;
|
||||||
|
origin_country?: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ discoverRoutes.get('/movies', async (req, res) => {
|
|||||||
const data = await tmdb.getDiscoverMovies({
|
const data = await tmdb.getDiscoverMovies({
|
||||||
page: Number(req.query.page),
|
page: Number(req.query.page),
|
||||||
language: req.query.language as string,
|
language: req.query.language as string,
|
||||||
|
genre: req.query.genre ? Number(req.query.genre) : undefined,
|
||||||
|
studio: req.query.studio ? Number(req.query.studio) : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const media = await Media.getRelatedMedia(
|
const media = await Media.getRelatedMedia(
|
||||||
@@ -99,6 +101,8 @@ discoverRoutes.get('/tv', async (req, res) => {
|
|||||||
const data = await tmdb.getDiscoverTv({
|
const data = await tmdb.getDiscoverTv({
|
||||||
page: Number(req.query.page),
|
page: Number(req.query.page),
|
||||||
language: req.query.language as string,
|
language: req.query.language as string,
|
||||||
|
genre: req.query.genre ? Number(req.query.genre) : undefined,
|
||||||
|
network: req.query.network ? Number(req.query.network) : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const media = await Media.getRelatedMedia(
|
const media = await Media.getRelatedMedia(
|
||||||
|
|||||||
@@ -74,6 +74,38 @@ router.get('/languages', isAuthenticated(), async (req, res) => {
|
|||||||
return res.status(200).json(languages);
|
return res.status(200).json(languages);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get<{ id: string }>('/studio/:id', async (req, res) => {
|
||||||
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
|
const studio = await tmdb.getStudio(Number(req.params.id));
|
||||||
|
|
||||||
|
return res.status(200).json(studio);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get<{ id: string }>('/network/:id', async (req, res) => {
|
||||||
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
|
const network = await tmdb.getNetwork(Number(req.params.id));
|
||||||
|
|
||||||
|
return res.status(200).json(network);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/genres/movie', isAuthenticated(), async (req, res) => {
|
||||||
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
|
const genres = await tmdb.getMovieGenres();
|
||||||
|
|
||||||
|
return res.status(200).json(genres);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/genres/tv', isAuthenticated(), async (req, res) => {
|
||||||
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
|
const genres = await tmdb.getTvGenres();
|
||||||
|
|
||||||
|
return res.status(200).json(genres);
|
||||||
|
});
|
||||||
|
|
||||||
router.get('/', (_req, res) => {
|
router.get('/', (_req, res) => {
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
api: 'Overseerr API',
|
api: 'Overseerr API',
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { useSWRInfinite } from 'swr';
|
import useSWR, { useSWRInfinite } from 'swr';
|
||||||
import type { MovieResult } from '../../../server/models/Search';
|
import type { MovieResult } from '../../../server/models/Search';
|
||||||
import ListView from '../Common/ListView';
|
import ListView from '../Common/ListView';
|
||||||
import { LanguageContext } from '../../context/LanguageContext';
|
import { LanguageContext } from '../../context/LanguageContext';
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import Header from '../Common/Header';
|
import Header from '../Common/Header';
|
||||||
import useSettings from '../../hooks/useSettings';
|
import useSettings from '../../hooks/useSettings';
|
||||||
import { MediaStatus } from '../../../server/constants/media';
|
import { MediaStatus } from '../../../server/constants/media';
|
||||||
import PageTitle from '../Common/PageTitle';
|
import PageTitle from '../Common/PageTitle';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import {
|
||||||
|
TmdbStudio,
|
||||||
|
TmdbGenre,
|
||||||
|
} from '../../../server/api/themoviedb/interfaces';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
discovermovies: 'Popular Movies',
|
discovermovies: 'Popular Movies',
|
||||||
|
genreMovies: '{genre} Movies',
|
||||||
|
studioMovies: '{studio} Movies',
|
||||||
});
|
});
|
||||||
|
|
||||||
interface SearchResult {
|
interface SearchResult {
|
||||||
@@ -21,16 +28,27 @@ interface SearchResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DiscoverMovies: React.FC = () => {
|
const DiscoverMovies: React.FC = () => {
|
||||||
|
const router = useRouter();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const { locale } = useContext(LanguageContext);
|
const { locale } = useContext(LanguageContext);
|
||||||
|
|
||||||
|
const { data: genres } = useSWR<TmdbGenre[]>('/api/v1/genres/movie');
|
||||||
|
const genre = genres?.find((g) => g.id === Number(router.query.genreId));
|
||||||
|
|
||||||
|
const { data: studio } = useSWR<TmdbStudio>(
|
||||||
|
`/api/v1/studio/${router.query.studioId}`
|
||||||
|
);
|
||||||
|
|
||||||
const { data, error, size, setSize } = useSWRInfinite<SearchResult>(
|
const { data, error, size, setSize } = useSWRInfinite<SearchResult>(
|
||||||
(pageIndex: number, previousPageData: SearchResult | null) => {
|
(pageIndex: number, previousPageData: SearchResult | null) => {
|
||||||
if (previousPageData && pageIndex + 1 > previousPageData.totalPages) {
|
if (previousPageData && pageIndex + 1 > previousPageData.totalPages) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `/api/v1/discover/movies?page=${pageIndex + 1}&language=${locale}`;
|
return `/api/v1/discover/movies?page=${pageIndex + 1}&language=${locale}${
|
||||||
|
genre ? `&genre=${genre.id}` : ''
|
||||||
|
}${studio ? `&studio=${studio.id}` : ''}`;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
initialSize: 3,
|
initialSize: 3,
|
||||||
@@ -68,13 +86,17 @@ const DiscoverMovies: React.FC = () => {
|
|||||||
const isReachingEnd =
|
const isReachingEnd =
|
||||||
isEmpty || (data && data[data.length - 1]?.results.length < 20);
|
isEmpty || (data && data[data.length - 1]?.results.length < 20);
|
||||||
|
|
||||||
|
const title = genre
|
||||||
|
? intl.formatMessage(messages.genreMovies, { genre: genre.name })
|
||||||
|
: studio
|
||||||
|
? intl.formatMessage(messages.studioMovies, { studio: studio.name })
|
||||||
|
: intl.formatMessage(messages.discovermovies);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageTitle title={intl.formatMessage(messages.discovermovies)} />
|
<PageTitle title={title} />
|
||||||
<div className="mt-1 mb-5">
|
<div className="mt-1 mb-5">
|
||||||
<Header>
|
<Header>{title}</Header>
|
||||||
<FormattedMessage {...messages.discovermovies} />
|
|
||||||
</Header>
|
|
||||||
</div>
|
</div>
|
||||||
<ListView
|
<ListView
|
||||||
items={titles}
|
items={titles}
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { useSWRInfinite } from 'swr';
|
import useSWR, { useSWRInfinite } from 'swr';
|
||||||
import type { TvResult } from '../../../server/models/Search';
|
import type { TvResult } from '../../../server/models/Search';
|
||||||
import ListView from '../Common/ListView';
|
import ListView from '../Common/ListView';
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { LanguageContext } from '../../context/LanguageContext';
|
import { LanguageContext } from '../../context/LanguageContext';
|
||||||
import Header from '../Common/Header';
|
import Header from '../Common/Header';
|
||||||
import useSettings from '../../hooks/useSettings';
|
import useSettings from '../../hooks/useSettings';
|
||||||
import { MediaStatus } from '../../../server/constants/media';
|
import { MediaStatus } from '../../../server/constants/media';
|
||||||
import PageTitle from '../Common/PageTitle';
|
import PageTitle from '../Common/PageTitle';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import {
|
||||||
|
TmdbGenre,
|
||||||
|
TmdbNetwork,
|
||||||
|
} from '../../../server/api/themoviedb/interfaces';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
discovertv: 'Popular Series',
|
discovertv: 'Popular Series',
|
||||||
|
genreSeries: '{genre} Series',
|
||||||
|
networkSeries: '{network} Series',
|
||||||
});
|
});
|
||||||
|
|
||||||
interface SearchResult {
|
interface SearchResult {
|
||||||
@@ -21,16 +28,27 @@ interface SearchResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DiscoverTv: React.FC = () => {
|
const DiscoverTv: React.FC = () => {
|
||||||
|
const router = useRouter();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const { locale } = useContext(LanguageContext);
|
const { locale } = useContext(LanguageContext);
|
||||||
|
|
||||||
|
const { data: genres } = useSWR<TmdbGenre[]>('/api/v1/genres/tv');
|
||||||
|
const genre = genres?.find((g) => g.id === Number(router.query.genreId));
|
||||||
|
|
||||||
|
const { data: network } = useSWR<TmdbNetwork>(
|
||||||
|
`/api/v1/network/${router.query.networkId}`
|
||||||
|
);
|
||||||
|
|
||||||
const { data, error, size, setSize } = useSWRInfinite<SearchResult>(
|
const { data, error, size, setSize } = useSWRInfinite<SearchResult>(
|
||||||
(pageIndex: number, previousPageData: SearchResult | null) => {
|
(pageIndex: number, previousPageData: SearchResult | null) => {
|
||||||
if (previousPageData && pageIndex + 1 > previousPageData.totalPages) {
|
if (previousPageData && pageIndex + 1 > previousPageData.totalPages) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `/api/v1/discover/tv?page=${pageIndex + 1}&language=${locale}`;
|
return `/api/v1/discover/tv?page=${pageIndex + 1}&language=${locale}${
|
||||||
|
genre ? `&genre=${genre.id}` : ''
|
||||||
|
}${network ? `&network=${network.id}` : ''}`;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
initialSize: 3,
|
initialSize: 3,
|
||||||
@@ -67,13 +85,17 @@ const DiscoverTv: React.FC = () => {
|
|||||||
const isReachingEnd =
|
const isReachingEnd =
|
||||||
isEmpty || (data && data[data.length - 1]?.results.length < 20);
|
isEmpty || (data && data[data.length - 1]?.results.length < 20);
|
||||||
|
|
||||||
|
const title = genre
|
||||||
|
? intl.formatMessage(messages.genreSeries, { genre: genre.name })
|
||||||
|
: network
|
||||||
|
? intl.formatMessage(messages.networkSeries, { network: network.name })
|
||||||
|
: intl.formatMessage(messages.discovertv);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageTitle title={intl.formatMessage(messages.discovertv)} />
|
<PageTitle title={title} />
|
||||||
<div className="mt-1 mb-5">
|
<div className="mt-1 mb-5">
|
||||||
<Header>
|
<Header>{title}</Header>
|
||||||
<FormattedMessage {...messages.discovertv} />
|
|
||||||
</Header>
|
|
||||||
</div>
|
</div>
|
||||||
<ListView
|
<ListView
|
||||||
items={titles}
|
items={titles}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useContext } from 'react';
|
|||||||
import { useSWRInfinite } from 'swr';
|
import { useSWRInfinite } from 'swr';
|
||||||
import type { TvResult } from '../../../server/models/Search';
|
import type { TvResult } from '../../../server/models/Search';
|
||||||
import ListView from '../Common/ListView';
|
import ListView from '../Common/ListView';
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { LanguageContext } from '../../context/LanguageContext';
|
import { LanguageContext } from '../../context/LanguageContext';
|
||||||
import Header from '../Common/Header';
|
import Header from '../Common/Header';
|
||||||
import useSettings from '../../hooks/useSettings';
|
import useSettings from '../../hooks/useSettings';
|
||||||
@@ -73,9 +73,7 @@ const DiscoverTvUpcoming: React.FC = () => {
|
|||||||
<>
|
<>
|
||||||
<PageTitle title={intl.formatMessage(messages.upcomingtv)} />
|
<PageTitle title={intl.formatMessage(messages.upcomingtv)} />
|
||||||
<div className="mt-1 mb-5">
|
<div className="mt-1 mb-5">
|
||||||
<Header>
|
<Header>{intl.formatMessage(messages.upcomingtv)}</Header>
|
||||||
<FormattedMessage {...messages.upcomingtv} />
|
|
||||||
</Header>
|
|
||||||
</div>
|
</div>
|
||||||
<ListView
|
<ListView
|
||||||
items={titles}
|
items={titles}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type {
|
|||||||
} from '../../../server/models/Search';
|
} from '../../../server/models/Search';
|
||||||
import ListView from '../Common/ListView';
|
import ListView from '../Common/ListView';
|
||||||
import { LanguageContext } from '../../context/LanguageContext';
|
import { LanguageContext } from '../../context/LanguageContext';
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import Header from '../Common/Header';
|
import Header from '../Common/Header';
|
||||||
import useSettings from '../../hooks/useSettings';
|
import useSettings from '../../hooks/useSettings';
|
||||||
import { MediaStatus } from '../../../server/constants/media';
|
import { MediaStatus } from '../../../server/constants/media';
|
||||||
@@ -78,9 +78,7 @@ const Trending: React.FC = () => {
|
|||||||
<>
|
<>
|
||||||
<PageTitle title={intl.formatMessage(messages.trending)} />
|
<PageTitle title={intl.formatMessage(messages.trending)} />
|
||||||
<div className="mt-1 mb-5">
|
<div className="mt-1 mb-5">
|
||||||
<Header>
|
<Header>{intl.formatMessage(messages.trending)}</Header>
|
||||||
<FormattedMessage {...messages.trending} />
|
|
||||||
</Header>
|
|
||||||
</div>
|
</div>
|
||||||
<ListView
|
<ListView
|
||||||
items={titles}
|
items={titles}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useSWRInfinite } from 'swr';
|
|||||||
import type { MovieResult } from '../../../server/models/Search';
|
import type { MovieResult } from '../../../server/models/Search';
|
||||||
import ListView from '../Common/ListView';
|
import ListView from '../Common/ListView';
|
||||||
import { LanguageContext } from '../../context/LanguageContext';
|
import { LanguageContext } from '../../context/LanguageContext';
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import Header from '../Common/Header';
|
import Header from '../Common/Header';
|
||||||
import useSettings from '../../hooks/useSettings';
|
import useSettings from '../../hooks/useSettings';
|
||||||
import { MediaStatus } from '../../../server/constants/media';
|
import { MediaStatus } from '../../../server/constants/media';
|
||||||
@@ -73,9 +73,7 @@ const UpcomingMovies: React.FC = () => {
|
|||||||
<>
|
<>
|
||||||
<PageTitle title={intl.formatMessage(messages.upcomingmovies)} />
|
<PageTitle title={intl.formatMessage(messages.upcomingmovies)} />
|
||||||
<div className="mt-1 mb-5">
|
<div className="mt-1 mb-5">
|
||||||
<Header>
|
<Header>{intl.formatMessage(messages.upcomingmovies)}</Header>
|
||||||
<FormattedMessage {...messages.upcomingmovies} />
|
|
||||||
</Header>
|
|
||||||
</div>
|
</div>
|
||||||
<ListView
|
<ListView
|
||||||
items={titles}
|
items={titles}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import useSWR from 'swr';
|
|||||||
import TmdbTitleCard from '../TitleCard/TmdbTitleCard';
|
import TmdbTitleCard from '../TitleCard/TmdbTitleCard';
|
||||||
import Slider from '../Slider';
|
import Slider from '../Slider';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import type { MediaResultsResponse } from '../../../server/interfaces/api/mediaInterfaces';
|
import type { MediaResultsResponse } from '../../../server/interfaces/api/mediaInterfaces';
|
||||||
import type { RequestResultsResponse } from '../../../server/interfaces/api/requestInterfaces';
|
import type { RequestResultsResponse } from '../../../server/interfaces/api/requestInterfaces';
|
||||||
import RequestCard from '../RequestCard';
|
import RequestCard from '../RequestCard';
|
||||||
@@ -42,9 +42,7 @@ const Discover: React.FC = () => {
|
|||||||
<div className="mt-6 mb-4 md:flex md:items-center md:justify-between">
|
<div className="mt-6 mb-4 md:flex md:items-center md:justify-between">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="inline-flex items-center text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate">
|
<div className="inline-flex items-center text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate">
|
||||||
<span>
|
<span>{intl.formatMessage(messages.recentlyAdded)}</span>
|
||||||
<FormattedMessage {...messages.recentlyAdded} />
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -64,9 +62,7 @@ const Discover: React.FC = () => {
|
|||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<Link href="/requests">
|
<Link href="/requests">
|
||||||
<a className="inline-flex items-center text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate">
|
<a className="inline-flex items-center text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate">
|
||||||
<span>
|
<span>{intl.formatMessage(messages.recentrequests)}</span>
|
||||||
<FormattedMessage {...messages.recentrequests} />
|
|
||||||
</span>
|
|
||||||
<svg
|
<svg
|
||||||
className="w-6 h-6 ml-2"
|
className="w-6 h-6 ml-2"
|
||||||
fill="none"
|
fill="none"
|
||||||
|
|||||||
@@ -187,7 +187,19 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (data.genres.length) {
|
if (data.genres.length) {
|
||||||
movieAttributes.push(data.genres.map((g) => g.name).join(', '));
|
movieAttributes.push(
|
||||||
|
data.genres
|
||||||
|
.map((g) => (
|
||||||
|
<Link href={`/discover/movies/genre/${g.id}`} key={`genre-${g.id}`}>
|
||||||
|
<a className="hover:underline">{g.name}</a>
|
||||||
|
</Link>
|
||||||
|
))
|
||||||
|
.reduce((prev, curr) => (
|
||||||
|
<>
|
||||||
|
{prev}, {curr}
|
||||||
|
</>
|
||||||
|
))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -660,7 +672,13 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
{intl.formatMessage(messages.studio)}
|
{intl.formatMessage(messages.studio)}
|
||||||
</span>
|
</span>
|
||||||
<span className="flex-1 text-sm text-right text-gray-400">
|
<span className="flex-1 text-sm text-right text-gray-400">
|
||||||
{data.productionCompanies[0]?.name}
|
<Link
|
||||||
|
href={`/discover/movies/studio/${data.productionCompanies[0].id}`}
|
||||||
|
>
|
||||||
|
<a className="hover:underline">
|
||||||
|
{data.productionCompanies[0].name}
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -189,7 +189,19 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (data.genres.length) {
|
if (data.genres.length) {
|
||||||
seriesAttributes.push(data.genres.map((g) => g.name).join(', '));
|
seriesAttributes.push(
|
||||||
|
data.genres
|
||||||
|
.map((g) => (
|
||||||
|
<Link href={`/discover/tv/genre/${g.id}`} key={`genre-${g.id}`}>
|
||||||
|
<a className="hover:underline">{g.name}</a>
|
||||||
|
</Link>
|
||||||
|
))
|
||||||
|
.reduce((prev, curr) => (
|
||||||
|
<>
|
||||||
|
{prev}, {curr}
|
||||||
|
</>
|
||||||
|
))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isComplete =
|
const isComplete =
|
||||||
@@ -684,7 +696,20 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
{intl.formatMessage(messages.network)}
|
{intl.formatMessage(messages.network)}
|
||||||
</span>
|
</span>
|
||||||
<span className="flex-1 text-sm text-right text-gray-400">
|
<span className="flex-1 text-sm text-right text-gray-400">
|
||||||
{data.networks.map((n) => n.name).join(', ')}
|
{data.networks
|
||||||
|
.map((n) => (
|
||||||
|
<Link
|
||||||
|
href={`/discover/tv/network/${n.id}`}
|
||||||
|
key={`network-${n.id}`}
|
||||||
|
>
|
||||||
|
<a className="hover:underline">{n.name}</a>
|
||||||
|
</Link>
|
||||||
|
))
|
||||||
|
.reduce((prev, curr) => (
|
||||||
|
<>
|
||||||
|
{prev}, {curr}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -17,11 +17,15 @@
|
|||||||
"components.Discover.discover": "Discover",
|
"components.Discover.discover": "Discover",
|
||||||
"components.Discover.discovermovies": "Popular Movies",
|
"components.Discover.discovermovies": "Popular Movies",
|
||||||
"components.Discover.discovertv": "Popular Series",
|
"components.Discover.discovertv": "Popular Series",
|
||||||
|
"components.Discover.genreMovies": "{genre} Movies",
|
||||||
|
"components.Discover.genreSeries": "{genre} Series",
|
||||||
|
"components.Discover.networkSeries": "{network} Series",
|
||||||
"components.Discover.nopending": "No Pending Requests",
|
"components.Discover.nopending": "No Pending Requests",
|
||||||
"components.Discover.popularmovies": "Popular Movies",
|
"components.Discover.popularmovies": "Popular Movies",
|
||||||
"components.Discover.populartv": "Popular Series",
|
"components.Discover.populartv": "Popular Series",
|
||||||
"components.Discover.recentlyAdded": "Recently Added",
|
"components.Discover.recentlyAdded": "Recently Added",
|
||||||
"components.Discover.recentrequests": "Recent Requests",
|
"components.Discover.recentrequests": "Recent Requests",
|
||||||
|
"components.Discover.studioMovies": "{studio} Movies",
|
||||||
"components.Discover.trending": "Trending",
|
"components.Discover.trending": "Trending",
|
||||||
"components.Discover.upcoming": "Upcoming Movies",
|
"components.Discover.upcoming": "Upcoming Movies",
|
||||||
"components.Discover.upcomingmovies": "Upcoming Movies",
|
"components.Discover.upcomingmovies": "Upcoming Movies",
|
||||||
|
|||||||
9
src/pages/discover/movies/genre/[genreId]/index.tsx
Normal file
9
src/pages/discover/movies/genre/[genreId]/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { NextPage } from 'next';
|
||||||
|
import DiscoverMovies from '../../../../../components/Discover/DiscoverMovies';
|
||||||
|
|
||||||
|
const DiscoverMoviesGenrePage: NextPage = () => {
|
||||||
|
return <DiscoverMovies />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DiscoverMoviesGenrePage;
|
||||||
9
src/pages/discover/movies/studio/[studioId]/index.tsx
Normal file
9
src/pages/discover/movies/studio/[studioId]/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { NextPage } from 'next';
|
||||||
|
import DiscoverMovies from '../../../../../components/Discover/DiscoverMovies';
|
||||||
|
|
||||||
|
const DiscoverMoviesStudioPage: NextPage = () => {
|
||||||
|
return <DiscoverMovies />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DiscoverMoviesStudioPage;
|
||||||
9
src/pages/discover/tv/genre/[genreId]/index.tsx
Normal file
9
src/pages/discover/tv/genre/[genreId]/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { NextPage } from 'next';
|
||||||
|
import DiscoverTv from '../../../../../components/Discover/DiscoverTv';
|
||||||
|
|
||||||
|
const DiscoverTvGenrePage: NextPage = () => {
|
||||||
|
return <DiscoverTv />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DiscoverTvGenrePage;
|
||||||
9
src/pages/discover/tv/network/[networkId]/index.tsx
Normal file
9
src/pages/discover/tv/network/[networkId]/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { NextPage } from 'next';
|
||||||
|
import DiscoverTv from '../../../../../components/Discover/DiscoverTv';
|
||||||
|
|
||||||
|
const DiscoverTvNetworkPage: NextPage = () => {
|
||||||
|
return <DiscoverTv />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DiscoverTvNetworkPage;
|
||||||
Reference in New Issue
Block a user