Added more maps to MediaType

This commit is contained in:
2025-10-23 14:53:17 +03:30
parent e9be428097
commit 18f264cbd4
7 changed files with 140 additions and 67 deletions
+6 -6
View File
@@ -1,12 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import type { MovieDetailsType } from '@/types/Movie' import type { MovieDetailsType } from '@/types/Movie'
import type { TvSeriesDetailsType } from '@/types/TvSeries' import type { TvSeriesDetailsType } from '@/types/TvSeries'
import { computed , ref } from 'vue' import { computed, ref } from 'vue'
import { useMediaStore } from '@/stores/movies' import { useMediaStore } from '@/stores/movies'
import type { MediaType } from '@/types/Media' import type { MediaType } from '@/types/Media'
const props = defineProps<{ const props = defineProps<{
type: 'movie' | 'tv' type: 'movie' | 'tv'
media?: MediaType
movie?: MovieDetailsType | null movie?: MovieDetailsType | null
tvSeries?: TvSeriesDetailsType | null tvSeries?: TvSeriesDetailsType | null
}>() }>()
@@ -103,24 +104,23 @@ const alreadyAdded = computed(() =>
</div> </div>
<!-- Actions --> <!-- Actions -->
<!-- TODO: Fix this -->
<div class="card-actions" v-auto-animate> <div class="card-actions" v-auto-animate>
<!-- <template v-if="props.type === 'movie' && props.movie"> <template v-if="(props.movie && props.media) || (props.tvSeries && props.media)">
<button <button
v-if="!alreadyAdded" v-if="!alreadyAdded"
class="btn px-6 bg-gradient-to-r from-gray-100 to-gray-200 border border-gray-300 text-gray-700 hover:from-gray-200 hover:to-gray-300" class="btn px-6 bg-gradient-to-r from-gray-100 to-gray-200 border border-gray-300 text-gray-700 hover:from-gray-200 hover:to-gray-300"
@click="store.addMedia(props.movie)" @click="store.addMedia(props.media)"
> >
Add to list Add to list
</button> </button>
<button <button
v-else v-else
class="btn px-6 bg-gradient-to-r from-red-50 to-red-100 border border-red-200 text-red-600 hover:from-red-100 hover:to-red-200" class="btn px-6 bg-gradient-to-r from-red-50 to-red-100 border border-red-200 text-red-600 hover:from-red-100 hover:to-red-200"
@click="store.removeMedia(props.movie.ImdbId)" @click="store.removeMedia(props.media.Id)"
> >
Remove from library Remove from library
</button> </button>
</template> --> </template>
<RouterLink <RouterLink
:to="{ :to="{
+1 -1
View File
@@ -9,7 +9,7 @@ export type MediaType = {
Overview: string Overview: string
PosterPath: string PosterPath: string
Popularity: number Popularity: number
ReleaseDate: string // For movies ReleaseDate: string // For movies
FirstAirDate: string // For TV series FirstAirDate: string // For TV series
VoteAverage: number VoteAverage: number
VoteCount: number VoteCount: number
+42 -2
View File
@@ -1,4 +1,6 @@
import type { MediaType } from "./Media" import type { MediaType } from './Media'
import type { MovieDetailsType } from './Movie'
import type { TvSeriesDetailsType } from './TvSeries'
type TMDBMedia = { type TMDBMedia = {
adult: boolean adult: boolean
@@ -34,4 +36,42 @@ export const mapMedia = (m: TMDBMedia): MediaType => ({
FirstAirDate: m.first_air_date ?? '', FirstAirDate: m.first_air_date ?? '',
VoteAverage: m.vote_average, VoteAverage: m.vote_average,
VoteCount: m.vote_count, VoteCount: m.vote_count,
}) })
export const mapMovieDetailsToMedia = (movie: MovieDetailsType): MediaType => {
return {
Adult: movie.Adult,
BackdropPath: movie.BackdropPath,
Id: movie.Id,
Title: movie.Title,
OriginalTitle: movie.OriginalTitle,
OriginalLanguage: movie.OriginalLanguage,
MediaType: 'movie', // since this mapper is for MovieDetailsType
Overview: movie.Overview,
PosterPath: movie.PosterPath,
Popularity: movie.Popularity,
ReleaseDate: movie.ReleaseDate,
FirstAirDate: '', // not applicable for movies
VoteAverage: movie.VoteAverage,
VoteCount: movie.VoteCount,
}
}
export const mapTvSeriesDetailsToMedia = (tv: TvSeriesDetailsType): MediaType => {
return {
Adult: tv.Adult,
BackdropPath: tv.BackdropPath,
Id: tv.Id,
Title: tv.Name, // TV shows use "Name"
OriginalTitle: tv.OriginalName,
OriginalLanguage: tv.OriginalLanguage,
MediaType: 'tv',
Overview: tv.Overview,
PosterPath: tv.PosterPath,
Popularity: tv.Popularity,
ReleaseDate: '', // not applicable for TV
FirstAirDate: tv.FirstAirDate,
VoteAverage: tv.VoteAverage,
VoteCount: tv.VoteCount,
}
}
+9 -9
View File
@@ -1,4 +1,4 @@
import type { MovieDetailsType } from "./Movie" import type { MovieDetailsType } from './Movie'
type TMDBMovieDetails = { type TMDBMovieDetails = {
adult: boolean adult: boolean
@@ -43,9 +43,9 @@ export const mapMovieDetails = (m: TMDBMovieDetails): MovieDetailsType => ({
Adult: m.adult, Adult: m.adult,
BackdropPath: m.backdrop_path ?? '', BackdropPath: m.backdrop_path ?? '',
Budget: m.budget, Budget: m.budget,
Genres: m.genres.map(genre => ({ Genres: m.genres.map((genre) => ({
id: genre.id, id: genre.id,
name: genre.name name: genre.name,
})), })),
Homepage: m.homepage, Homepage: m.homepage,
Id: m.id, Id: m.id,
@@ -55,23 +55,23 @@ export const mapMovieDetails = (m: TMDBMovieDetails): MovieDetailsType => ({
Overview: m.overview, Overview: m.overview,
Popularity: m.popularity, Popularity: m.popularity,
PosterPath: m.poster_path ?? '', PosterPath: m.poster_path ?? '',
ProductionCompanies: m.production_companies.map(company => ({ ProductionCompanies: m.production_companies.map((company) => ({
id: company.id, id: company.id,
logoPath: company.logo_path, logoPath: company.logo_path,
name: company.name, name: company.name,
originCountry: company.origin_country originCountry: company.origin_country,
})), })),
ProductionCountries: m.production_countries.map(country => ({ ProductionCountries: m.production_countries.map((country) => ({
iso31661: country.iso_3166_1, iso31661: country.iso_3166_1,
name: country.name name: country.name,
})), })),
ReleaseDate: m.release_date, ReleaseDate: m.release_date,
Revenue: m.revenue, Revenue: m.revenue,
Runtime: m.runtime ?? 0, Runtime: m.runtime ?? 0,
SpokenLanguages: m.spoken_languages.map(lang => ({ SpokenLanguages: m.spoken_languages.map((lang) => ({
englishName: lang.english_name, englishName: lang.english_name,
iso6391: lang.iso_639_1, iso6391: lang.iso_639_1,
name: lang.name name: lang.name,
})), })),
Status: m.status, Status: m.status,
Tagline: m.tagline ?? '', Tagline: m.tagline ?? '',
+31 -29
View File
@@ -1,4 +1,4 @@
import type { TvSeriesDetailsType } from "./TvSeries" import type { TvSeriesDetailsType } from './TvSeries'
type TMDBTvSeriesDetails = { type TMDBTvSeriesDetails = {
adult: boolean adult: boolean
@@ -82,44 +82,46 @@ type TMDBTvSeriesDetails = {
export const mapTvSeriesDetails = (tv: TMDBTvSeriesDetails): TvSeriesDetailsType => ({ export const mapTvSeriesDetails = (tv: TMDBTvSeriesDetails): TvSeriesDetailsType => ({
Adult: tv.adult, Adult: tv.adult,
BackdropPath: tv.backdrop_path ?? '', BackdropPath: tv.backdrop_path ?? '',
CreatedBy: tv.created_by.map(creator => ({ CreatedBy: tv.created_by.map((creator) => ({
id: creator.id, id: creator.id,
creditId: creator.credit_id, creditId: creator.credit_id,
name: creator.name, name: creator.name,
gender: creator.gender, gender: creator.gender,
profilePath: creator.profile_path profilePath: creator.profile_path,
})), })),
EpisodeRunTime: tv.episode_run_time, EpisodeRunTime: tv.episode_run_time,
FirstAirDate: tv.first_air_date ?? '', FirstAirDate: tv.first_air_date ?? '',
Genres: tv.genres.map(genre => ({ Genres: tv.genres.map((genre) => ({
id: genre.id, id: genre.id,
name: genre.name name: genre.name,
})), })),
Homepage: tv.homepage, Homepage: tv.homepage,
Id: tv.id, Id: tv.id,
InProduction: tv.in_production, InProduction: tv.in_production,
Languages: tv.languages, Languages: tv.languages,
LastAirDate: tv.last_air_date ?? '', LastAirDate: tv.last_air_date ?? '',
LastEpisodeToAir: tv.last_episode_to_air ? { LastEpisodeToAir: tv.last_episode_to_air
id: tv.last_episode_to_air.id, ? {
name: tv.last_episode_to_air.name, id: tv.last_episode_to_air.id,
overview: tv.last_episode_to_air.overview, name: tv.last_episode_to_air.name,
voteAverage: tv.last_episode_to_air.vote_average, overview: tv.last_episode_to_air.overview,
voteCount: tv.last_episode_to_air.vote_count, voteAverage: tv.last_episode_to_air.vote_average,
airDate: tv.last_episode_to_air.air_date ?? '', voteCount: tv.last_episode_to_air.vote_count,
episodeNumber: tv.last_episode_to_air.episode_number, airDate: tv.last_episode_to_air.air_date ?? '',
productionCode: tv.last_episode_to_air.production_code, episodeNumber: tv.last_episode_to_air.episode_number,
runtime: tv.last_episode_to_air.runtime ?? 0, productionCode: tv.last_episode_to_air.production_code,
seasonNumber: tv.last_episode_to_air.season_number, runtime: tv.last_episode_to_air.runtime ?? 0,
showId: tv.last_episode_to_air.show_id, seasonNumber: tv.last_episode_to_air.season_number,
stillPath: tv.last_episode_to_air.still_path showId: tv.last_episode_to_air.show_id,
} : null, stillPath: tv.last_episode_to_air.still_path,
}
: null,
Name: tv.name, Name: tv.name,
Networks: tv.networks.map(network => ({ Networks: tv.networks.map((network) => ({
id: network.id, id: network.id,
logoPath: network.logo_path, logoPath: network.logo_path,
name: network.name, name: network.name,
originCountry: network.origin_country originCountry: network.origin_country,
})), })),
NumberOfEpisodes: tv.number_of_episodes, NumberOfEpisodes: tv.number_of_episodes,
NumberOfSeasons: tv.number_of_seasons, NumberOfSeasons: tv.number_of_seasons,
@@ -129,17 +131,17 @@ export const mapTvSeriesDetails = (tv: TMDBTvSeriesDetails): TvSeriesDetailsType
Overview: tv.overview, Overview: tv.overview,
Popularity: tv.popularity, Popularity: tv.popularity,
PosterPath: tv.poster_path ?? '', PosterPath: tv.poster_path ?? '',
ProductionCompanies: tv.production_companies.map(company => ({ ProductionCompanies: tv.production_companies.map((company) => ({
id: company.id, id: company.id,
logoPath: company.logo_path, logoPath: company.logo_path,
name: company.name, name: company.name,
originCountry: company.origin_country originCountry: company.origin_country,
})), })),
ProductionCountries: tv.production_countries.map(country => ({ ProductionCountries: tv.production_countries.map((country) => ({
iso31661: country.iso_3166_1, iso31661: country.iso_3166_1,
name: country.name name: country.name,
})), })),
Seasons: tv.seasons.map(season => ({ Seasons: tv.seasons.map((season) => ({
airDate: season.air_date, airDate: season.air_date,
episodeCount: season.episode_count, episodeCount: season.episode_count,
id: season.id, id: season.id,
@@ -147,12 +149,12 @@ export const mapTvSeriesDetails = (tv: TMDBTvSeriesDetails): TvSeriesDetailsType
overview: season.overview, overview: season.overview,
posterPath: season.poster_path, posterPath: season.poster_path,
seasonNumber: season.season_number, seasonNumber: season.season_number,
voteAverage: season.vote_average voteAverage: season.vote_average,
})), })),
SpokenLanguages: tv.spoken_languages.map(lang => ({ SpokenLanguages: tv.spoken_languages.map((lang) => ({
englishName: lang.english_name, englishName: lang.english_name,
iso6391: lang.iso_639_1, iso6391: lang.iso_639_1,
name: lang.name name: lang.name,
})), })),
Status: tv.status, Status: tv.status,
Tagline: tv.tagline, Tagline: tv.tagline,
+50 -19
View File
@@ -1,46 +1,77 @@
<script setup lang="ts"> <script setup lang="ts">
import ErrorAlert from '@/components/alerts/ErrorAlert.vue'
import MediaDetails from '@/components/MediaDetails.vue' import MediaDetails from '@/components/MediaDetails.vue'
import { getMovieDetails, getSeriesDetails } from '@/lib/api' import { getMovieDetails, getSeriesDetails } from '@/lib/api'
import { mapMovieDetailsToMedia, mapTvSeriesDetailsToMedia } from '@/types/MediaMap'
import type { MovieDetailsType } from '@/types/Movie' import type { MovieDetailsType } from '@/types/Movie'
import type { TvSeriesDetailsType } from '@/types/TvSeries' import type { TvSeriesDetailsType } from '@/types/TvSeries'
import { onMounted, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
const route = useRoute() const route = useRoute()
const movie = ref<MovieDetailsType | null>() const movieDetails = ref<MovieDetailsType | null>(null)
const tvSeries = ref<TvSeriesDetailsType | null>() const tvSeriesDetails = ref<TvSeriesDetailsType | null>(null)
const isLoading = ref(false)
const errorMessage = ref<string | null>(null)
const media = computed(() => {
if (movieDetails.value) {
return mapMovieDetailsToMedia(movieDetails.value)
} else if (tvSeriesDetails.value) {
return mapTvSeriesDetailsToMedia(tvSeriesDetails.value)
} else {
return undefined
}
})
onMounted(async () => { onMounted(async () => {
if (!route.params.id || !route.params.type) {
errorMessage.value = 'Invalid route parameters'
return
}
isLoading.value = true
errorMessage.value = null
try { try {
if (route.params.id && route.params.type === 'movie') { if (route.params.type === 'movie') {
movie.value = await getMovieDetails(route.params.id as string) movieDetails.value = await getMovieDetails(route.params.id as string)
} else if (route.params.id && route.params.type === 'tv') { } else if (route.params.type === 'tv') {
tvSeries.value = await getSeriesDetails(route.params.id as string) tvSeriesDetails.value = await getSeriesDetails(route.params.id as string)
} else { } else {
errorMessage.value = 'Unsupported media type'
} }
} catch (error) { } catch (err: unknown) {
console.error(error) console.error(err)
errorMessage.value = 'Failed to load media details'
} finally {
isLoading.value = false
} }
}) })
</script> </script>
<template> <template>
<div class="container mx-auto px-4 py-8"> <div class="container mx-auto px-4 py-8">
<div <!-- Error -->
v-if="(route.params.type === 'movie' && !movie) || (route.params.type === 'tv' && !tvSeries)" <ErrorAlert v-if="errorMessage" :message="errorMessage" />
class="flex justify-center items-center"
> <!-- Loading -->
<div v-else-if="isLoading" class="flex justify-center items-center">
<span class="loading loading-ring loading-lg text-primary"></span> <span class="loading loading-ring loading-lg text-primary"></span>
</div> </div>
<!-- Details -->
<MediaDetails <MediaDetails
v-if="movie || tvSeries" v-else-if="movieDetails || tvSeriesDetails"
:type="(route.params.type as 'movie' | 'tv') || undefined" :type="(route.params.type as 'movie' | 'tv')"
:movie="movie" :media="media"
:tv-series="tvSeries" :movie="movieDetails"
:tv-series="tvSeriesDetails"
v-motion-fade-visible-once v-motion-fade-visible-once
/> />
<!-- Fallback if nothing found -->
<ErrorAlert v-else message="Could not find media" />
</div> </div>
</template> </template>
<style scoped></style>
+1 -1
View File
@@ -17,7 +17,7 @@ const isLoaded = ref(false)
<!-- Title with Experimental badge --> <!-- Title with Experimental badge -->
<div class="flex items-center gap-3 mb-6"> <div class="flex items-center gap-3 mb-6">
<h1 class="text-3xl font-bold text-gray-700"> <h1 class="text-3xl font-bold text-gray-700">
Watch Watch <br />
<span class="bg-gradient-to-r from-indigo-500 to-cyan-400 bg-clip-text text-transparent"> <span class="bg-gradient-to-r from-indigo-500 to-cyan-400 bg-clip-text text-transparent">
{{ movieName }} {{ movieName }}
</span> </span>