mirror of
				https://github.com/mmahdium/TBW.git
				synced 2025-11-04 09:09:24 +01:00 
			
		
		
		
	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">
 | 
			
		||||
import { useMoviesStore } from '@/stores/movies'
 | 
			
		||||
import { useMediaStore } from '@/stores/movies'
 | 
			
		||||
import { computed, ref } from 'vue'
 | 
			
		||||
import type { MovieType } from '@/types/Movie'
 | 
			
		||||
import type { MediaType } from '@/types/Media'
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
  movie: MovieType
 | 
			
		||||
  movie: MediaType
 | 
			
		||||
}>()
 | 
			
		||||
 | 
			
		||||
const store = useMoviesStore()
 | 
			
		||||
const store = useMediaStore()
 | 
			
		||||
const imageLoadFailed = 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(() => {
 | 
			
		||||
  if (!props.movie.PosterPath) {
 | 
			
		||||
@@ -27,7 +27,9 @@ const imageSource = computed(() => {
 | 
			
		||||
    v-motion-fade-visible-once
 | 
			
		||||
  >
 | 
			
		||||
    <!-- 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">
 | 
			
		||||
        <span v-if="!loaded" class="loading loading-ring loading-lg text-primary"></span>
 | 
			
		||||
 | 
			
		||||
@@ -66,7 +68,9 @@ const imageSource = computed(() => {
 | 
			
		||||
 | 
			
		||||
    <!-- Body -->
 | 
			
		||||
    <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
 | 
			
		||||
          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-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"
 | 
			
		||||
          @click="store.addMovie(props.movie)"
 | 
			
		||||
          @click="store.addMedia(props.movie)"
 | 
			
		||||
        >
 | 
			
		||||
          Add
 | 
			
		||||
        </button>
 | 
			
		||||
@@ -88,7 +92,7 @@ const imageSource = computed(() => {
 | 
			
		||||
          v-motion-fade-visible-once
 | 
			
		||||
          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"
 | 
			
		||||
          @click="store.removeMovie(props.movie.Id)"
 | 
			
		||||
          @click="store.removeMedia(props.movie.Id)"
 | 
			
		||||
        >
 | 
			
		||||
          Remove
 | 
			
		||||
        </button>
 | 
			
		||||
							
								
								
									
										156
									
								
								src/components/MediaDetails.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								src/components/MediaDetails.vue
									
									
									
									
									
										Normal file
									
								
							@@ -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">
 | 
			
		||||
import AddMoreCard from './AddMoreCard.vue'
 | 
			
		||||
import MovieCard from './MovieCard.vue'
 | 
			
		||||
import type { MovieType } from '@/types/Movie'
 | 
			
		||||
import MediaCard from './MediaCard.vue'
 | 
			
		||||
import type { MediaType } from '@/types/Media'
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
  movies: MovieType[]
 | 
			
		||||
  movies: MediaType[]
 | 
			
		||||
  loadingMore: 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"
 | 
			
		||||
    >
 | 
			
		||||
      <li v-for="movie in props.movies" :key="movie.Id" v-auto-animate>
 | 
			
		||||
        <MovieCard :movie="movie" />
 | 
			
		||||
        <MediaCard :movie="movie" />
 | 
			
		||||
      </li>
 | 
			
		||||
      <li v-if="!props.isSearch">
 | 
			
		||||
        <AddMoreCard />
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,9 @@
 | 
			
		||||
import type { MediaResponseType } from '@/types/Media'
 | 
			
		||||
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'
 | 
			
		||||
 | 
			
		||||
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> => {
 | 
			
		||||
    const response = await instance.get(`/search/multi`, {
 | 
			
		||||
  const response = await instance.get(`/search/multi`, {
 | 
			
		||||
    params: {
 | 
			
		||||
      query: query,
 | 
			
		||||
      include_adult: true,
 | 
			
		||||
      page: page
 | 
			
		||||
      page: page,
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
@@ -81,22 +85,26 @@ export const loadMoreMovies = async (query: string, page: number): Promise<Media
 | 
			
		||||
  return data
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const getMovieDetails = async (id: string) => {
 | 
			
		||||
  const response = await instance.get(`movie/${id}`, {
 | 
			
		||||
  })
 | 
			
		||||
export const getMovieDetails = async (id: string): Promise<MovieDetailsType | null> => {
 | 
			
		||||
  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) => {
 | 
			
		||||
  const response = await instance.get(``, {
 | 
			
		||||
    params: {
 | 
			
		||||
      i: id,
 | 
			
		||||
      plot: 'full',
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
export const getSeriesDetails = async (id: string): Promise<TvSeriesDetailsType | null> => {
 | 
			
		||||
  const response = await instance.get(`/tv/${id}`, {})
 | 
			
		||||
 | 
			
		||||
  return response.data
 | 
			
		||||
  if (response.status !== 200) {
 | 
			
		||||
    return null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const data: TvSeriesDetailsType = mapTvSeriesDetails(response.data)
 | 
			
		||||
 | 
			
		||||
  return data
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,45 +1,43 @@
 | 
			
		||||
import { ref } from 'vue'
 | 
			
		||||
import { defineStore } from 'pinia'
 | 
			
		||||
import type { MovieType } from '@/types/Movie'
 | 
			
		||||
import type { MediaType } from '@/types/Media'
 | 
			
		||||
 | 
			
		||||
function saveMovies(movies: MovieType[]) {
 | 
			
		||||
  localStorage.setItem('movies', JSON.stringify(movies))
 | 
			
		||||
function saveMedias(medias: MediaType[]) {
 | 
			
		||||
  localStorage.setItem('medias', JSON.stringify(medias))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function loadMovies(): MovieType[] {
 | 
			
		||||
  const movies = localStorage.getItem('movies')
 | 
			
		||||
  return movies ? JSON.parse(movies) : []
 | 
			
		||||
function loadMedias(): MediaType[] {
 | 
			
		||||
  const medias = localStorage.getItem('medias')
 | 
			
		||||
  return medias ? JSON.parse(medias) : []
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const useMoviesStore = defineStore('movies', () => {
 | 
			
		||||
  const movieList = ref<MovieType[]>(loadMovies())
 | 
			
		||||
export const useMediaStore = defineStore('medias', () => {
 | 
			
		||||
  const mediaList = ref<MediaType[]>(loadMedias())
 | 
			
		||||
 | 
			
		||||
  function addMovie(movie: MovieType) {
 | 
			
		||||
    if (!movieList.value.find((m) => m.Id === movie.Id)) {
 | 
			
		||||
      movieList.value.push(movie)
 | 
			
		||||
      saveMovies(movieList.value)
 | 
			
		||||
  function addMedia(media: MediaType) {
 | 
			
		||||
    if (!mediaList.value.find((m) => m.Id === media.Id)) {
 | 
			
		||||
      mediaList.value.push(media)
 | 
			
		||||
      saveMedias(mediaList.value)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function removeMovie(movieId: number) {
 | 
			
		||||
    movieList.value = movieList.value.filter((m) => m.Id !== movieId)
 | 
			
		||||
    saveMovies(movieList.value)
 | 
			
		||||
  function removeMedia(mediaId: number) {
 | 
			
		||||
    mediaList.value = mediaList.value.filter((m) => m.Id !== mediaId)
 | 
			
		||||
    saveMedias(mediaList.value)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return { movieList, addMovie, removeMovie }
 | 
			
		||||
  return { mediaList, addMedia, removeMedia }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// TODO: add state for search page
 | 
			
		||||
 | 
			
		||||
export const useSearchPageStore = defineStore('searchPage', () => {
 | 
			
		||||
  const movieList = ref<MovieType[]>()
 | 
			
		||||
  const mediaList = ref<MediaType[]>()
 | 
			
		||||
  const searchPage = ref(0)
 | 
			
		||||
  const searchQuery = ref('')
 | 
			
		||||
 | 
			
		||||
  function setState(page: number, query: string, movies: MovieType[]) {
 | 
			
		||||
  function setState(page: number, query: string, medias: MediaType[]) {
 | 
			
		||||
    searchPage.value = page
 | 
			
		||||
    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 SearchBar from '@/components/SearchBar.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 { useSearchPageStore } from '@/stores/movies'
 | 
			
		||||
 | 
			
		||||
const movies = ref<MovieType[]>()
 | 
			
		||||
const movies = ref<MediaType[]>()
 | 
			
		||||
const seachError = ref<string>('')
 | 
			
		||||
const searchQuery = ref('')
 | 
			
		||||
const searchPage = ref(1)
 | 
			
		||||
@@ -59,12 +59,12 @@ onMounted(() => {
 | 
			
		||||
  if (
 | 
			
		||||
    state.searchQuery !== '' &&
 | 
			
		||||
    state.searchPage !== 0 &&
 | 
			
		||||
    state.movieList !== undefined &&
 | 
			
		||||
    state.movieList.length > 0
 | 
			
		||||
    state.mediaList !== undefined &&
 | 
			
		||||
    state.mediaList.length > 0
 | 
			
		||||
  ) {
 | 
			
		||||
    searchQuery.value = state.searchQuery
 | 
			
		||||
    searchPage.value = state.searchPage
 | 
			
		||||
    movies.value = state.movieList
 | 
			
		||||
    movies.value = state.mediaList
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,40 +1,45 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import MovieDetails from '@/components/MovieDetails.vue'
 | 
			
		||||
import { getMovie } from '@/lib/api'
 | 
			
		||||
import MediaDetails from '@/components/MediaDetails.vue'
 | 
			
		||||
import { getMovieDetails, getSeriesDetails } from '@/lib/api'
 | 
			
		||||
import type { MovieDetailsType } from '@/types/Movie'
 | 
			
		||||
import type { TvSeriesDetailsType } from '@/types/TvSeries'
 | 
			
		||||
import { onMounted, ref } from 'vue'
 | 
			
		||||
import { useRoute } from 'vue-router'
 | 
			
		||||
 | 
			
		||||
const route = useRoute()
 | 
			
		||||
 | 
			
		||||
const movie = ref<MovieDetailsType>()
 | 
			
		||||
const movie = ref<MovieDetailsType | null>()
 | 
			
		||||
const tvSeries = ref<TvSeriesDetailsType | null>()
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
  try {
 | 
			
		||||
    const response = await getMovie(route.params.id as string)
 | 
			
		||||
    movie.value = response
 | 
			
		||||
    if (route.params.id && route.params.type === 'movie') {
 | 
			
		||||
      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) {
 | 
			
		||||
    console.error(error)
 | 
			
		||||
    movie.value = {
 | 
			
		||||
      Title: '',
 | 
			
		||||
      Year: '',
 | 
			
		||||
      imdbID: '',
 | 
			
		||||
      Type: '',
 | 
			
		||||
      Poster: '',
 | 
			
		||||
      Plot: '',
 | 
			
		||||
      Language: '',
 | 
			
		||||
      Country: '',
 | 
			
		||||
      imdbRating: '',
 | 
			
		||||
      Error: true,
 | 
			
		||||
      ErrorMessage: (error as Error).message,
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <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>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { useMoviesStore } from '@/stores/movies'
 | 
			
		||||
import { useMediaStore } from '@/stores/movies'
 | 
			
		||||
import MovieList from '@/components/MovieList.vue'
 | 
			
		||||
 | 
			
		||||
const store = useMoviesStore()
 | 
			
		||||
const store = useMediaStore()
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
@@ -14,8 +14,8 @@ const store = useMoviesStore()
 | 
			
		||||
    </h1>
 | 
			
		||||
    <MovieList
 | 
			
		||||
      v-auto-animate
 | 
			
		||||
      v-if="store.movieList"
 | 
			
		||||
      :movies="store.movieList"
 | 
			
		||||
      v-if="store.mediaList"
 | 
			
		||||
      :movies="store.mediaList"
 | 
			
		||||
      :loading-more="false"
 | 
			
		||||
      :is-search="false"
 | 
			
		||||
    />
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user