mirror of
https://github.com/mmahdium/TBW.git
synced 2026-08-14 16:22:48 +03:30
refactor: rename MovieCard to MediaCard and update to support multi-media types
- Rename MovieCard.vue to MediaCard.vue and update all related references - Add MediaDetails.vue component to handle both movie and TV series details - Remove old MovieDetails.vue component - Update MovieList.vue to use MediaCard instead of MovieCard - Modify API functions to handle both movie and TV series details - Update store from useMoviesStore to useMediaStore with new naming conventions - Update type references from MovieType to MediaType - Add support for TV series details in DetailsView.vue Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
@@ -1,17 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useMoviesStore } from '@/stores/movies'
|
import { useMediaStore } from '@/stores/movies'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import type { MovieType } from '@/types/Movie'
|
import type { MediaType } from '@/types/Media'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
movie: MovieType
|
movie: MediaType
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const store = useMoviesStore()
|
const store = useMediaStore()
|
||||||
const imageLoadFailed = ref(false)
|
const imageLoadFailed = ref(false)
|
||||||
const loaded = ref(false)
|
const loaded = ref(false)
|
||||||
|
|
||||||
const alreadyAdded = computed(() => store.movieList.some((movie) => movie.Id === props.movie.Id))
|
const alreadyAdded = computed(() => store.mediaList.some((media) => media.Id === props.movie.Id))
|
||||||
|
|
||||||
const imageSource = computed(() => {
|
const imageSource = computed(() => {
|
||||||
if (!props.movie.PosterPath) {
|
if (!props.movie.PosterPath) {
|
||||||
@@ -27,7 +27,9 @@ const imageSource = computed(() => {
|
|||||||
v-motion-fade-visible-once
|
v-motion-fade-visible-once
|
||||||
>
|
>
|
||||||
<!-- Poster -->
|
<!-- Poster -->
|
||||||
<router-link :to="{ name: 'details', params: { type: props.movie.MediaType, id: props.movie.Id } }">
|
<router-link
|
||||||
|
:to="{ name: 'details', params: { type: props.movie.MediaType, id: props.movie.Id } }"
|
||||||
|
>
|
||||||
<figure class="overflow-hidden flex items-center justify-center aspect-[2/3] bg-gray-50">
|
<figure class="overflow-hidden flex items-center justify-center aspect-[2/3] bg-gray-50">
|
||||||
<span v-if="!loaded" class="loading loading-ring loading-lg text-primary"></span>
|
<span v-if="!loaded" class="loading loading-ring loading-lg text-primary"></span>
|
||||||
|
|
||||||
@@ -66,7 +68,9 @@ const imageSource = computed(() => {
|
|||||||
|
|
||||||
<!-- Body -->
|
<!-- Body -->
|
||||||
<div class="card-body p-4 flex flex-col">
|
<div class="card-body p-4 flex flex-col">
|
||||||
<router-link :to="{ name: 'details', params: { type: props.movie.MediaType, id: props.movie.Id } }">
|
<router-link
|
||||||
|
:to="{ name: 'details', params: { type: props.movie.MediaType, id: props.movie.Id } }"
|
||||||
|
>
|
||||||
<h2
|
<h2
|
||||||
class="card-title text-base font-semibold bg-gradient-to-r from-gray-700 to-gray-500 bg-clip-text text-transparent"
|
class="card-title text-base font-semibold bg-gradient-to-r from-gray-700 to-gray-500 bg-clip-text text-transparent"
|
||||||
>
|
>
|
||||||
@@ -80,7 +84,7 @@ const imageSource = computed(() => {
|
|||||||
v-motion-fade-visible-once
|
v-motion-fade-visible-once
|
||||||
v-if="!alreadyAdded"
|
v-if="!alreadyAdded"
|
||||||
class="btn btn-sm px-4 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 hover:text-gray-900 transition"
|
class="btn btn-sm px-4 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 hover:text-gray-900 transition"
|
||||||
@click="store.addMovie(props.movie)"
|
@click="store.addMedia(props.movie)"
|
||||||
>
|
>
|
||||||
Add
|
Add
|
||||||
</button>
|
</button>
|
||||||
@@ -88,7 +92,7 @@ const imageSource = computed(() => {
|
|||||||
v-motion-fade-visible-once
|
v-motion-fade-visible-once
|
||||||
v-else
|
v-else
|
||||||
class="btn btn-sm px-4 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 hover:text-red-700 transition"
|
class="btn btn-sm px-4 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 hover:text-red-700 transition"
|
||||||
@click="store.removeMovie(props.movie.Id)"
|
@click="store.removeMedia(props.movie.Id)"
|
||||||
>
|
>
|
||||||
Remove
|
Remove
|
||||||
</button>
|
</button>
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { MovieDetailsType } from '@/types/Movie'
|
||||||
|
import type { TvSeriesDetailsType } from '@/types/TvSeries'
|
||||||
|
import { computed , ref } from 'vue'
|
||||||
|
import { useMediaStore } from '@/stores/movies'
|
||||||
|
import type { MediaType } from '@/types/Media'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
type: 'movie' | 'tv'
|
||||||
|
movie?: MovieDetailsType | null
|
||||||
|
tvSeries?: TvSeriesDetailsType | null
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const imageLoaded = ref(false)
|
||||||
|
const store = useMediaStore()
|
||||||
|
|
||||||
|
const alreadyAdded = computed(() =>
|
||||||
|
props.type === 'movie' && props.movie
|
||||||
|
? store.mediaList.some((m: MediaType) => m.Id === props.movie!.Id)
|
||||||
|
: props.type === 'tv' && props.tvSeries
|
||||||
|
? store.mediaList.some((ts: MediaType) => ts.Id === props.tvSeries!.Id)
|
||||||
|
: false,
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- Error -->
|
||||||
|
<!-- <ErrorAlert
|
||||||
|
v-if="props.type === 'movie' && props.movie?.Error"
|
||||||
|
:message="props.movie.ErrorMessage"
|
||||||
|
/> -->
|
||||||
|
|
||||||
|
<!-- Hero -->
|
||||||
|
<div
|
||||||
|
class="flex flex-col lg:flex-row gap-12 items-center max-w-6xl w-full bg-white/70 backdrop-blur-md border border-gray-200/60 shadow-md rounded-xl p-8 transition"
|
||||||
|
v-motion-fade-visible-once
|
||||||
|
>
|
||||||
|
<!-- Poster -->
|
||||||
|
<figure class="flex-shrink-0">
|
||||||
|
<span v-if="!imageLoaded" class="loading loading-ring loading-lg text-primary"></span>
|
||||||
|
<img
|
||||||
|
v-show="imageLoaded"
|
||||||
|
:src="
|
||||||
|
type === 'movie'
|
||||||
|
? 'https://image.tmdb.org/t/p/w500' + props.movie!.PosterPath
|
||||||
|
: 'https://image.tmdb.org/t/p/w500' + props.tvSeries!.PosterPath
|
||||||
|
"
|
||||||
|
alt="Poster"
|
||||||
|
class="w-full max-w-sm rounded-lg shadow-lg transform transition-transform duration-500 hover:scale-105"
|
||||||
|
@load="imageLoaded = true"
|
||||||
|
/>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<!-- Text -->
|
||||||
|
<div class="flex-1">
|
||||||
|
<h1 class="text-4xl font-bold text-gray-800 mb-2">
|
||||||
|
<template v-if="props.type === 'movie'">
|
||||||
|
{{ props.movie!.Title }}
|
||||||
|
<span class="text-gray-400 text-lg font-normal"
|
||||||
|
>({{ props.movie!.ReleaseDate?.slice(0, 4) }})</span
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ props.tvSeries!.Name }}
|
||||||
|
<span class="text-gray-400 text-lg font-normal"
|
||||||
|
>({{ props.tvSeries!.FirstAirDate?.slice(0, 4) }})</span
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p v-if="props.type === 'movie' && props.movie!.Tagline" class="italic text-gray-500 mb-2">
|
||||||
|
{{ props.movie!.Tagline }}
|
||||||
|
</p>
|
||||||
|
<p v-if="props.type === 'tv' && props.tvSeries!.Tagline" class="italic text-gray-500 mb-2">
|
||||||
|
{{ props.tvSeries!.Tagline }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="text-gray-600 leading-relaxed mb-6">
|
||||||
|
{{ props.type === 'movie' ? props.movie!.Overview : props.tvSeries!.Overview }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Badges -->
|
||||||
|
<div class="flex flex-wrap gap-2 mb-6">
|
||||||
|
<span
|
||||||
|
v-for="g in props.type === 'movie' ? props.movie!.Genres : props.tvSeries!.Genres"
|
||||||
|
:key="g.id"
|
||||||
|
class="px-3 py-1 rounded-md text-sm bg-gradient-to-r from-gray-100 to-gray-200 text-gray-700 border border-gray-300"
|
||||||
|
>
|
||||||
|
{{ g.name }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="props.type === 'movie'"
|
||||||
|
class="px-3 py-1 rounded-md text-sm bg-gradient-to-r from-gray-100 to-gray-200 text-gray-700 border border-gray-300"
|
||||||
|
>
|
||||||
|
Runtime: {{ props.movie!.Runtime }} min
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="props.type === 'tv'"
|
||||||
|
class="px-3 py-1 rounded-md text-sm bg-gradient-to-r from-gray-100 to-gray-200 text-gray-700 border border-gray-300"
|
||||||
|
>
|
||||||
|
Seasons: {{ props.tvSeries!.NumberOfSeasons }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<!-- TODO: Fix this -->
|
||||||
|
<div class="card-actions" v-auto-animate>
|
||||||
|
<!-- <template v-if="props.type === 'movie' && props.movie">
|
||||||
|
<button
|
||||||
|
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"
|
||||||
|
@click="store.addMedia(props.movie)"
|
||||||
|
>
|
||||||
|
Add to list
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
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"
|
||||||
|
@click="store.removeMedia(props.movie.ImdbId)"
|
||||||
|
>
|
||||||
|
Remove from library
|
||||||
|
</button>
|
||||||
|
</template> -->
|
||||||
|
|
||||||
|
<RouterLink
|
||||||
|
:to="{
|
||||||
|
name: 'watch',
|
||||||
|
params: {
|
||||||
|
name: props.type === 'movie' ? props.movie!.Title : props.tvSeries!.Name,
|
||||||
|
id: props.type === 'movie' ? props.movie!.Id : props.tvSeries!.Id,
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn relative flex items-center gap-2 px-6 bg-gradient-to-r from-indigo-500 to-violet-500 text-white border-0 shadow-md hover:opacity-90 transition"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-5 w-5"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M6.5 5.5a1 1 0 0 1 1.52-.85l6 4.5a1 1 0 0 1 0 1.7l-6 4.5A1 1 0 0 1 6.5 14.5v-9z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>Watch</span>
|
||||||
|
<span class="badge badge-sm badge-secondary absolute -top-2 -right-2">Beta</span>
|
||||||
|
</button>
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import type { MovieDetailsType } from '@/types/Movie'
|
|
||||||
import { computed, ref } from 'vue'
|
|
||||||
import ErrorAlert from './alerts/ErrorAlert.vue'
|
|
||||||
import { useMoviesStore } from '@/stores/movies'
|
|
||||||
|
|
||||||
const props = defineProps<{ movie: MovieDetailsType | undefined }>()
|
|
||||||
|
|
||||||
const imageLoaded = ref(false)
|
|
||||||
const store = useMoviesStore()
|
|
||||||
|
|
||||||
const alreadyAdded = computed(() =>
|
|
||||||
props.movie ? store.movieList.some((movie) => movie.imdbID === props.movie!.imdbID) : false,
|
|
||||||
)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<ErrorAlert v-if="props.movie?.Error" :message="props.movie.ErrorMessage" />
|
|
||||||
|
|
||||||
<!-- Loading state -->
|
|
||||||
<div v-if="props.movie === undefined" class="flex justify-center items-center">
|
|
||||||
<span class="loading loading-ring loading-lg text-primary"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Hero content -->
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="flex flex-col lg:flex-row gap-12 items-center max-w-6xl w-full bg-white/70 backdrop-blur-md border border-gray-200/60 shadow-md rounded-xl p-8 transition"
|
|
||||||
v-motion-fade-visible-once
|
|
||||||
>
|
|
||||||
<!-- Poster -->
|
|
||||||
<figure class="flex-shrink-0">
|
|
||||||
<span v-if="!imageLoaded" class="loading loading-ring loading-lg text-primary"></span>
|
|
||||||
<img
|
|
||||||
v-show="imageLoaded"
|
|
||||||
:src="props.movie.Poster"
|
|
||||||
alt="Poster"
|
|
||||||
class="w-full max-w-sm rounded-lg shadow-lg transform transition-transform duration-500 hover:scale-105"
|
|
||||||
@load="imageLoaded = true"
|
|
||||||
/>
|
|
||||||
</figure>
|
|
||||||
|
|
||||||
<!-- Text -->
|
|
||||||
<div class="flex-1">
|
|
||||||
<h1 class="text-4xl font-bold text-gray-800 mb-4">
|
|
||||||
{{ props.movie.Title }}
|
|
||||||
<span class="text-gray-400 text-lg font-normal">({{ props.movie.Year }})</span>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<p class="text-gray-600 leading-relaxed mb-6">{{ props.movie.Plot }}</p>
|
|
||||||
|
|
||||||
<!-- Badges -->
|
|
||||||
<div class="flex flex-wrap gap-2 mb-6">
|
|
||||||
<span
|
|
||||||
class="px-3 py-1 rounded-md text-sm bg-gradient-to-r from-gray-100 to-gray-200 text-gray-700 border border-gray-300"
|
|
||||||
>
|
|
||||||
Language: {{ props.movie.Language }}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="px-3 py-1 rounded-md text-sm bg-gradient-to-r from-gray-100 to-gray-200 text-gray-700 border border-gray-300"
|
|
||||||
>
|
|
||||||
Country: {{ props.movie.Country }}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="px-3 py-1 rounded-md text-sm bg-gradient-to-r from-gray-100 to-gray-200 text-gray-700 border border-gray-300"
|
|
||||||
>
|
|
||||||
IMDB Rating: {{ props.movie.imdbRating }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Actions -->
|
|
||||||
<div class="card-actions" v-auto-animate>
|
|
||||||
<button
|
|
||||||
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"
|
|
||||||
@click="store.addMovie(props.movie)"
|
|
||||||
>
|
|
||||||
Add to list
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
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"
|
|
||||||
@click="store.removeMovie(props.movie.imdbID)"
|
|
||||||
>
|
|
||||||
Remove from library
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<RouterLink
|
|
||||||
:to="{ name: 'watch', params: { name: props.movie.Title, id: props.movie.imdbID } }"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="btn relative flex items-center gap-2 px-6 bg-gradient-to-r from-indigo-500 to-violet-500 text-white border-0 shadow-md hover:opacity-90 transition"
|
|
||||||
>
|
|
||||||
<!-- Icon -->
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="h-5 w-5"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M6.5 5.5a1 1 0 0 1 1.52-.85l6 4.5a1 1 0 0 1 0 1.7l-6 4.5A1 1 0 0 1 6.5 14.5v-9z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<span>Watch</span>
|
|
||||||
<span class="badge badge-sm badge-secondary absolute -top-2 -right-2">Beta</span>
|
|
||||||
</button>
|
|
||||||
</RouterLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import AddMoreCard from './AddMoreCard.vue'
|
import AddMoreCard from './AddMoreCard.vue'
|
||||||
import MovieCard from './MovieCard.vue'
|
import MediaCard from './MediaCard.vue'
|
||||||
import type { MovieType } from '@/types/Movie'
|
import type { MediaType } from '@/types/Media'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
movies: MovieType[]
|
movies: MediaType[]
|
||||||
loadingMore: boolean
|
loadingMore: boolean
|
||||||
isSearch: boolean
|
isSearch: boolean
|
||||||
}>()
|
}>()
|
||||||
@@ -30,7 +30,7 @@ const emit = defineEmits<{ (e: 'loaded', id: string): void; (e: 'loadMore'): voi
|
|||||||
class="grid gap-4 grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5"
|
class="grid gap-4 grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5"
|
||||||
>
|
>
|
||||||
<li v-for="movie in props.movies" :key="movie.Id" v-auto-animate>
|
<li v-for="movie in props.movies" :key="movie.Id" v-auto-animate>
|
||||||
<MovieCard :movie="movie" />
|
<MediaCard :movie="movie" />
|
||||||
</li>
|
</li>
|
||||||
<li v-if="!props.isSearch">
|
<li v-if="!props.isSearch">
|
||||||
<AddMoreCard />
|
<AddMoreCard />
|
||||||
|
|||||||
+22
-14
@@ -1,5 +1,9 @@
|
|||||||
import type { MediaResponseType } from '@/types/Media'
|
import type { MediaResponseType } from '@/types/Media'
|
||||||
import { mapMedia } from '@/types/MediaMap'
|
import { mapMedia } from '@/types/MediaMap'
|
||||||
|
import type { MovieDetailsType } from '@/types/Movie'
|
||||||
|
import { mapMovieDetails } from '@/types/MovieMap'
|
||||||
|
import type { TvSeriesDetailsType } from '@/types/TvSeries'
|
||||||
|
import { mapTvSeriesDetails } from '@/types/TvSeriesMap'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
const TMDB_READ_API_KEY =
|
const TMDB_READ_API_KEY =
|
||||||
@@ -48,11 +52,11 @@ export const searchMovies = async (query: string): Promise<MediaResponseType> =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const loadMoreMovies = async (query: string, page: number): Promise<MediaResponseType> => {
|
export const loadMoreMovies = async (query: string, page: number): Promise<MediaResponseType> => {
|
||||||
const response = await instance.get(`/search/multi`, {
|
const response = await instance.get(`/search/multi`, {
|
||||||
params: {
|
params: {
|
||||||
query: query,
|
query: query,
|
||||||
include_adult: true,
|
include_adult: true,
|
||||||
page: page
|
page: page,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -81,22 +85,26 @@ export const loadMoreMovies = async (query: string, page: number): Promise<Media
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getMovieDetails = async (id: string) => {
|
export const getMovieDetails = async (id: string): Promise<MovieDetailsType | null> => {
|
||||||
const response = await instance.get(`movie/${id}`, {
|
const response = await instance.get(`/movie/${id}`, {})
|
||||||
})
|
|
||||||
|
|
||||||
|
if (response.status !== 200) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: MovieDetailsType = mapMovieDetails(response.data)
|
||||||
|
|
||||||
return response.data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getSeriesDetails = async (id: string) => {
|
export const getSeriesDetails = async (id: string): Promise<TvSeriesDetailsType | null> => {
|
||||||
const response = await instance.get(``, {
|
const response = await instance.get(`/tv/${id}`, {})
|
||||||
params: {
|
|
||||||
i: id,
|
|
||||||
plot: 'full',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return response.data
|
if (response.status !== 200) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: TvSeriesDetailsType = mapTvSeriesDetails(response.data)
|
||||||
|
|
||||||
|
return data
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-22
@@ -1,45 +1,43 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import type { MovieType } from '@/types/Movie'
|
import type { MediaType } from '@/types/Media'
|
||||||
|
|
||||||
function saveMovies(movies: MovieType[]) {
|
function saveMedias(medias: MediaType[]) {
|
||||||
localStorage.setItem('movies', JSON.stringify(movies))
|
localStorage.setItem('medias', JSON.stringify(medias))
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadMovies(): MovieType[] {
|
function loadMedias(): MediaType[] {
|
||||||
const movies = localStorage.getItem('movies')
|
const medias = localStorage.getItem('medias')
|
||||||
return movies ? JSON.parse(movies) : []
|
return medias ? JSON.parse(medias) : []
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useMoviesStore = defineStore('movies', () => {
|
export const useMediaStore = defineStore('medias', () => {
|
||||||
const movieList = ref<MovieType[]>(loadMovies())
|
const mediaList = ref<MediaType[]>(loadMedias())
|
||||||
|
|
||||||
function addMovie(movie: MovieType) {
|
function addMedia(media: MediaType) {
|
||||||
if (!movieList.value.find((m) => m.Id === movie.Id)) {
|
if (!mediaList.value.find((m) => m.Id === media.Id)) {
|
||||||
movieList.value.push(movie)
|
mediaList.value.push(media)
|
||||||
saveMovies(movieList.value)
|
saveMedias(mediaList.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeMovie(movieId: number) {
|
function removeMedia(mediaId: number) {
|
||||||
movieList.value = movieList.value.filter((m) => m.Id !== movieId)
|
mediaList.value = mediaList.value.filter((m) => m.Id !== mediaId)
|
||||||
saveMovies(movieList.value)
|
saveMedias(mediaList.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { movieList, addMovie, removeMovie }
|
return { mediaList, addMedia, removeMedia }
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: add state for search page
|
|
||||||
|
|
||||||
export const useSearchPageStore = defineStore('searchPage', () => {
|
export const useSearchPageStore = defineStore('searchPage', () => {
|
||||||
const movieList = ref<MovieType[]>()
|
const mediaList = ref<MediaType[]>()
|
||||||
const searchPage = ref(0)
|
const searchPage = ref(0)
|
||||||
const searchQuery = ref('')
|
const searchQuery = ref('')
|
||||||
|
|
||||||
function setState(page: number, query: string, movies: MovieType[]) {
|
function setState(page: number, query: string, medias: MediaType[]) {
|
||||||
searchPage.value = page
|
searchPage.value = page
|
||||||
searchQuery.value = query
|
searchQuery.value = query
|
||||||
movieList.value = movies
|
mediaList.value = medias
|
||||||
}
|
}
|
||||||
return { searchPage, searchQuery, movieList, setState }
|
return { searchPage, searchQuery, mediaList, setState }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { loadMoreMovies, searchMovies } from '@/lib/api'
|
|||||||
import { onMounted, ref, watch } from 'vue'
|
import { onMounted, ref, watch } from 'vue'
|
||||||
import SearchBar from '@/components/SearchBar.vue'
|
import SearchBar from '@/components/SearchBar.vue'
|
||||||
import MovieList from '@/components/MovieList.vue'
|
import MovieList from '@/components/MovieList.vue'
|
||||||
import type { MovieType } from '@/types/Movie'
|
import type { MediaType } from '@/types/Media'
|
||||||
import ErrorAlert from '@/components/alerts/ErrorAlert.vue'
|
import ErrorAlert from '@/components/alerts/ErrorAlert.vue'
|
||||||
import { useSearchPageStore } from '@/stores/movies'
|
import { useSearchPageStore } from '@/stores/movies'
|
||||||
|
|
||||||
const movies = ref<MovieType[]>()
|
const movies = ref<MediaType[]>()
|
||||||
const seachError = ref<string>('')
|
const seachError = ref<string>('')
|
||||||
const searchQuery = ref('')
|
const searchQuery = ref('')
|
||||||
const searchPage = ref(1)
|
const searchPage = ref(1)
|
||||||
@@ -59,12 +59,12 @@ onMounted(() => {
|
|||||||
if (
|
if (
|
||||||
state.searchQuery !== '' &&
|
state.searchQuery !== '' &&
|
||||||
state.searchPage !== 0 &&
|
state.searchPage !== 0 &&
|
||||||
state.movieList !== undefined &&
|
state.mediaList !== undefined &&
|
||||||
state.movieList.length > 0
|
state.mediaList.length > 0
|
||||||
) {
|
) {
|
||||||
searchQuery.value = state.searchQuery
|
searchQuery.value = state.searchQuery
|
||||||
searchPage.value = state.searchPage
|
searchPage.value = state.searchPage
|
||||||
movies.value = state.movieList
|
movies.value = state.mediaList
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+24
-19
@@ -1,40 +1,45 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import MovieDetails from '@/components/MovieDetails.vue'
|
import MediaDetails from '@/components/MediaDetails.vue'
|
||||||
import { getMovie } from '@/lib/api'
|
import { getMovieDetails, getSeriesDetails } from '@/lib/api'
|
||||||
import type { MovieDetailsType } from '@/types/Movie'
|
import type { MovieDetailsType } from '@/types/Movie'
|
||||||
|
import type { TvSeriesDetailsType } from '@/types/TvSeries'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
const movie = ref<MovieDetailsType>()
|
const movie = ref<MovieDetailsType | null>()
|
||||||
|
const tvSeries = ref<TvSeriesDetailsType | null>()
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
const response = await getMovie(route.params.id as string)
|
if (route.params.id && route.params.type === 'movie') {
|
||||||
movie.value = response
|
movie.value = await getMovieDetails(route.params.id as string)
|
||||||
|
} else if (route.params.id && route.params.type === 'tv') {
|
||||||
|
tvSeries.value = await getSeriesDetails(route.params.id as string)
|
||||||
|
} else {
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
movie.value = {
|
|
||||||
Title: '',
|
|
||||||
Year: '',
|
|
||||||
imdbID: '',
|
|
||||||
Type: '',
|
|
||||||
Poster: '',
|
|
||||||
Plot: '',
|
|
||||||
Language: '',
|
|
||||||
Country: '',
|
|
||||||
imdbRating: '',
|
|
||||||
Error: true,
|
|
||||||
ErrorMessage: (error as Error).message,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container mx-auto px-4 py-8">
|
<div class="container mx-auto px-4 py-8">
|
||||||
<MovieDetails :movie="movie" v-motion-fade-visible-once />
|
<div
|
||||||
|
v-if="(route.params.type === 'movie' && !movie) || (route.params.type === 'tv' && !tvSeries)"
|
||||||
|
class="flex justify-center items-center"
|
||||||
|
>
|
||||||
|
<span class="loading loading-ring loading-lg text-primary"></span>
|
||||||
|
</div>
|
||||||
|
<MediaDetails
|
||||||
|
v-if="movie || tvSeries"
|
||||||
|
:type="(route.params.type as 'movie' | 'tv') || undefined"
|
||||||
|
:movie="movie"
|
||||||
|
:tv-series="tvSeries"
|
||||||
|
v-motion-fade-visible-once
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useMoviesStore } from '@/stores/movies'
|
import { useMediaStore } from '@/stores/movies'
|
||||||
import MovieList from '@/components/MovieList.vue'
|
import MovieList from '@/components/MovieList.vue'
|
||||||
|
|
||||||
const store = useMoviesStore()
|
const store = useMediaStore()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -14,8 +14,8 @@ const store = useMoviesStore()
|
|||||||
</h1>
|
</h1>
|
||||||
<MovieList
|
<MovieList
|
||||||
v-auto-animate
|
v-auto-animate
|
||||||
v-if="store.movieList"
|
v-if="store.mediaList"
|
||||||
:movies="store.movieList"
|
:movies="store.mediaList"
|
||||||
:loading-more="false"
|
:loading-more="false"
|
||||||
:is-search="false"
|
:is-search="false"
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user