Added improved types

This commit is contained in:
2025-10-22 21:12:20 +03:30
parent 138e7c3248
commit 72271d1de2
12 changed files with 509 additions and 71 deletions

View File

@@ -11,9 +11,14 @@ const store = useMoviesStore()
const imageLoadFailed = ref(false)
const loaded = ref(false)
const alreadyAdded = computed(() =>
store.movieList.some((movie) => movie.imdbID === props.movie.imdbID),
)
const alreadyAdded = computed(() => store.movieList.some((movie) => movie.Id === props.movie.Id))
const imageSource = computed(() => {
if (!props.movie.PosterPath) {
return ''
}
return `https://image.tmdb.org/t/p/w300${props.movie.PosterPath}`
})
</script>
<template>
@@ -22,7 +27,7 @@ const alreadyAdded = computed(() =>
v-motion-fade-visible-once
>
<!-- Poster -->
<router-link :to="{ name: 'details', params: { id: props.movie.imdbID } }">
<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>
@@ -45,7 +50,7 @@ const alreadyAdded = computed(() =>
<img
v-show="loaded && !imageLoadFailed"
:src="props.movie.Poster"
:src="imageSource"
:alt="props.movie.Title"
class="object-cover w-full h-full transform transition-transform duration-500 hover:scale-105"
@load="loaded = true"
@@ -61,13 +66,13 @@ const alreadyAdded = computed(() =>
<!-- Body -->
<div class="card-body p-4 flex flex-col">
<router-link :to="{ name: 'details', params: { id: props.movie.imdbID } }">
<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"
>
{{ props.movie.Title }}
</h2>
<p class="text-sm text-gray-400">{{ props.movie.Year }}</p>
<p class="text-sm text-gray-400">{{ props.movie.ReleaseDate.slice(0, 4) }}</p>
</router-link>
<div class="card-actions justify-end mt-auto">
@@ -83,7 +88,7 @@ const alreadyAdded = 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.imdbID)"
@click="store.removeMovie(props.movie.Id)"
>
Remove
</button>

View File

@@ -29,10 +29,10 @@ const emit = defineEmits<{ (e: 'loaded', id: string): void; (e: 'loadMore'): voi
v-else
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.imdbID" v-auto-animate>
<li v-for="movie in props.movies" :key="movie.Id" v-auto-animate>
<MovieCard :movie="movie" />
</li>
<li>
<li v-if="!props.isSearch">
<AddMoreCard />
</li>
</ul>

View File

@@ -1,53 +1,96 @@
import type { MovieResponseType, MovieType } from '@/types/Movie'
import type { MediaResponseType } from '@/types/Media'
import { mapMedia } from '@/types/MediaMap'
import axios from 'axios'
const API_KEY = '595695c3' // I know this should not be here and I dont care
const TMDB_READ_API_KEY =
'eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIzZjY3MDNmM2UyYTBiMmI0MGZlNGZiYjNlMTU0NjI0NCIsIm5iZiI6MTc2MDU3NDcwNi45MjQsInN1YiI6IjY4ZjAzY2YyNGZmNGM0NjI1NmM3N2EyNyIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.HHReb_7Oue_o1wkgH2mmbCgdMJ8buFfRdCtwQqj_1Us' // I know this should not be here and I dont care
const instance = axios.create({
baseURL: 'https://www.omdbapi.com/',
baseURL: 'https://api.themoviedb.org/3',
timeout: 6969,
params: {
apikey: API_KEY,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${TMDB_READ_API_KEY}`,
},
})
export const searchMovies = async (query: string): Promise<MovieResponseType> => {
const response = await instance.get(``, {
export const searchMovies = async (query: string): Promise<MediaResponseType> => {
const response = await instance.get(`/search/multi`, {
params: {
s: query,
query: query,
include_adult: true,
},
})
// if (response.data.Response === 'False') {
// throw new Error(response.data.Error)
// }
if (response.status !== 200) {
return {
Results: [],
Page: 0,
totalResults: 0,
totalPages: 0,
ErrorMessage: response.data.Error,
}
}
const data: MovieResponseType = {
Search: response.data.Search as MovieType[],
totalResults: response.data.totalResults,
Response: response.data.Response === 'True',
ErrorMessage: response.data.Error || '',
const filtered = response.data.results.filter((result: { media_type: string }) => {
return result.media_type === 'movie' || result.media_type === 'tv'
})
const data: MediaResponseType = {
Results: filtered.map(mapMedia),
Page: response.data.page,
totalResults: response.data.total_results,
totalPages: response.data.total_pages,
ErrorMessage: '',
}
return data
}
export const loadMoreMovies = async (query: string, page: number) => {
const response = await instance.get(``, {
export const loadMoreMovies = async (query: string, page: number): Promise<MediaResponseType> => {
const response = await instance.get(`/search/multi`, {
params: {
s: query,
page: page,
query: query,
include_adult: true,
page: page
},
})
if (response.data.Response === 'False') {
throw new Error(response.data.Error)
if (response.status !== 200) {
return {
Results: [],
Page: 0,
totalResults: 0,
totalPages: 0,
ErrorMessage: response.data.Error,
}
}
const filtered = response.data.results.filter((result: { media_type: string }) => {
return result.media_type === 'movie' || result.media_type === 'tv'
})
const data: MediaResponseType = {
Results: filtered.map(mapMedia),
Page: response.data.page,
totalResults: response.data.total_results,
totalPages: response.data.total_pages,
ErrorMessage: '',
}
return data
}
export const getMovieDetails = async (id: string) => {
const response = await instance.get(`movie/${id}`, {
})
return response.data
}
export const getMovie = async (id: string) => {
export const getSeriesDetails = async (id: string) => {
const response = await instance.get(``, {
params: {
i: id,
@@ -55,9 +98,5 @@ export const getMovie = async (id: string) => {
},
})
if (response.data.Response === 'False') {
throw new Error(response.data.Error)
}
return response.data
}

View File

@@ -17,7 +17,7 @@ const routes: RouteRecordRaw[] = [
component: () => import('@/views/ListView.vue'),
},
{
path: '/details/:id',
path: '/details/:type/:id',
name: 'details',
component: () => import('@/views/DetailsView.vue'),
},

View File

@@ -15,14 +15,14 @@ export const useMoviesStore = defineStore('movies', () => {
const movieList = ref<MovieType[]>(loadMovies())
function addMovie(movie: MovieType) {
if (!movieList.value.find((m) => m.imdbID === movie.imdbID)) {
if (!movieList.value.find((m) => m.Id === movie.Id)) {
movieList.value.push(movie)
saveMovies(movieList.value)
}
}
function removeMovie(movieId: string) {
movieList.value = movieList.value.filter((m) => m.imdbID !== movieId)
function removeMovie(movieId: number) {
movieList.value = movieList.value.filter((m) => m.Id !== movieId)
saveMovies(movieList.value)
}

24
src/types/Media.ts Normal file
View File

@@ -0,0 +1,24 @@
export type MediaType = {
Adult: boolean
BackdropPath: string
Id: number
Title: string
OriginalTitle: string
OriginalLanguage: string
MediaType: 'movie' | 'tv' | 'person' | 'collection'
Overview: string
PosterPath: string
Popularity: number
ReleaseDate: string // For movies
FirstAirDate: string // For TV series
VoteAverage: number
VoteCount: number
}
export type MediaResponseType = {
Results: MediaType[]
Page: number
totalResults: number
totalPages: number
ErrorMessage: string
}

37
src/types/MediaMap.ts Normal file
View File

@@ -0,0 +1,37 @@
import type { MediaType } from "./Media"
type TMDBMedia = {
adult: boolean
backdrop_path: string | null
id: number
title?: string
name?: string
original_title?: string
original_name?: string
original_language: string
overview: string
poster_path: string | null
popularity: number
release_date?: string
first_air_date?: string
vote_average: number
vote_count: number
media_type: 'movie' | 'tv' | 'person' | 'collection'
}
export const mapMedia = (m: TMDBMedia): MediaType => ({
Adult: m.adult,
BackdropPath: m.backdrop_path ?? '',
Id: m.id,
Title: m.title ?? m.name ?? '',
OriginalTitle: m.original_title ?? m.original_name ?? '',
OriginalLanguage: m.original_language,
MediaType: m.media_type,
Overview: m.overview,
PosterPath: m.poster_path ?? '',
Popularity: m.popularity,
ReleaseDate: m.release_date ?? '',
FirstAirDate: m.first_air_date ?? '',
VoteAverage: m.vote_average,
VoteCount: m.vote_count,
})

View File

@@ -1,28 +1,38 @@
export type MovieType = {
Title: string
Year: string
imdbID: string
Type: string
Poster: string
}
export type MovieResponseType = {
Search: MovieType[]
totalResults: string
Response: boolean
ErrorMessage: string
}
export type MovieDetailsType = {
Adult: boolean
BackdropPath: string
Budget: number
Genres: Array<{ id: number; name: string }>
Homepage: string
Id: number
ImdbId: string
OriginalLanguage: string
OriginalTitle: string
Overview: string
Popularity: number
PosterPath: string
ProductionCompanies: Array<{
id: number
logoPath: string | null
name: string
originCountry: string
}>
ProductionCountries: Array<{
iso31661: string
name: string
}>
ReleaseDate: string
Revenue: number
Runtime: number
SpokenLanguages: Array<{
englishName: string
iso6391: string
name: string
}>
Status: string
Tagline: string
Title: string
Year: string
imdbID: string
Type: string
Poster: string
Plot: string
Language: string
Country: string
imdbRating: string
Error: boolean
ErrorMessage: string
Video: boolean
VoteAverage: number
VoteCount: number
}

82
src/types/MovieMap.ts Normal file
View File

@@ -0,0 +1,82 @@
import type { MovieDetailsType } from "./Movie"
type TMDBMovieDetails = {
adult: boolean
backdrop_path: string | null
budget: number
genres: Array<{ id: number; name: string }>
homepage: string
id: number
imdb_id: string
original_language: string
original_title: string
overview: string
popularity: number
poster_path: string | null
production_companies: Array<{
id: number
logo_path: string | null
name: string
origin_country: string
}>
production_countries: Array<{
iso_3166_1: string
name: string
}>
release_date: string
revenue: number
runtime: number
spoken_languages: Array<{
english_name: string
iso_639_1: string
name: string
}>
status: string
tagline: string
title: string
video: boolean
vote_average: number
vote_count: number
}
export const mapMovieDetails = (m: TMDBMovieDetails): MovieDetailsType => ({
Adult: m.adult,
BackdropPath: m.backdrop_path ?? '',
Budget: m.budget,
Genres: m.genres.map(genre => ({
id: genre.id,
name: genre.name
})),
Homepage: m.homepage,
Id: m.id,
ImdbId: m.imdb_id,
OriginalLanguage: m.original_language,
OriginalTitle: m.original_title,
Overview: m.overview,
Popularity: m.popularity,
PosterPath: m.poster_path ?? '',
ProductionCompanies: m.production_companies.map(company => ({
id: company.id,
logoPath: company.logo_path,
name: company.name,
originCountry: company.origin_country
})),
ProductionCountries: m.production_countries.map(country => ({
iso31661: country.iso_3166_1,
name: country.name
})),
ReleaseDate: m.release_date,
Revenue: m.revenue,
Runtime: m.runtime ?? 0,
SpokenLanguages: m.spoken_languages.map(lang => ({
englishName: lang.english_name,
iso6391: lang.iso_639_1,
name: lang.name
})),
Status: m.status,
Tagline: m.tagline ?? '',
Title: m.title,
Video: m.video,
VoteAverage: m.vote_average,
VoteCount: m.vote_count,
})

78
src/types/TvSeries.ts Normal file
View File

@@ -0,0 +1,78 @@
export type TvSeriesDetailsType = {
Adult: boolean
BackdropPath: string
CreatedBy: Array<{
id: number
creditId: string
name: string
gender: number
profilePath: string | null
}>
EpisodeRunTime: number[]
FirstAirDate: string
Genres: Array<{ id: number; name: string }>
Homepage: string
Id: number
InProduction: boolean
Languages: string[]
LastAirDate: string
LastEpisodeToAir: {
id: number
name: string
overview: string
voteAverage: number
voteCount: number
airDate: string
episodeNumber: number
productionCode: string
runtime: number
seasonNumber: number
showId: number
stillPath: string | null
} | null
Name: string
Networks: Array<{
id: number
logoPath: string | null
name: string
originCountry: string
}>
NumberOfEpisodes: number
NumberOfSeasons: number
OriginCountry: string[]
OriginalLanguage: string
OriginalName: string
Overview: string
Popularity: number
PosterPath: string
ProductionCompanies: Array<{
id: number
logoPath: string | null
name: string
originCountry: string
}>
ProductionCountries: Array<{
iso31661: string
name: string
}>
Seasons: Array<{
airDate: string | null
episodeCount: number
id: number
name: string
overview: string
posterPath: string | null
seasonNumber: number
voteAverage: number
}>
SpokenLanguages: Array<{
englishName: string
iso6391: string
name: string
}>
Status: string
Tagline: string
Type: string
VoteAverage: number
VoteCount: number
}

162
src/types/TvSeriesMap.ts Normal file
View File

@@ -0,0 +1,162 @@
import type { TvSeriesDetailsType } from "./TvSeries"
type TMDBTvSeriesDetails = {
adult: boolean
backdrop_path: string | null
created_by: Array<{
id: number
credit_id: string
name: string
gender: number
profile_path: string | null
}>
episode_run_time: number[]
first_air_date: string
genres: Array<{ id: number; name: string }>
homepage: string
id: number
in_production: boolean
languages: string[]
last_air_date: string
last_episode_to_air: {
id: number
name: string
overview: string
vote_average: number
vote_count: number
air_date: string
episode_number: number
production_code: string
runtime: number
season_number: number
show_id: number
still_path: string | null
} | null
name: string
networks: Array<{
id: number
logo_path: string | null
name: string
origin_country: string
}>
number_of_episodes: number
number_of_seasons: number
origin_country: string[]
original_language: string
original_name: string
overview: string
popularity: number
poster_path: string | null
production_companies: Array<{
id: number
logo_path: string | null
name: string
origin_country: string
}>
production_countries: Array<{
iso_3166_1: string
name: string
}>
seasons: Array<{
air_date: string | null
episode_count: number
id: number
name: string
overview: string
poster_path: string | null
season_number: number
vote_average: number
}>
spoken_languages: Array<{
english_name: string
iso_639_1: string
name: string
}>
status: string
tagline: string
type: string
vote_average: number
vote_count: number
}
export const mapTvSeriesDetails = (tv: TMDBTvSeriesDetails): TvSeriesDetailsType => ({
Adult: tv.adult,
BackdropPath: tv.backdrop_path ?? '',
CreatedBy: tv.created_by.map(creator => ({
id: creator.id,
creditId: creator.credit_id,
name: creator.name,
gender: creator.gender,
profilePath: creator.profile_path
})),
EpisodeRunTime: tv.episode_run_time,
FirstAirDate: tv.first_air_date ?? '',
Genres: tv.genres.map(genre => ({
id: genre.id,
name: genre.name
})),
Homepage: tv.homepage,
Id: tv.id,
InProduction: tv.in_production,
Languages: tv.languages,
LastAirDate: tv.last_air_date ?? '',
LastEpisodeToAir: tv.last_episode_to_air ? {
id: tv.last_episode_to_air.id,
name: tv.last_episode_to_air.name,
overview: tv.last_episode_to_air.overview,
voteAverage: tv.last_episode_to_air.vote_average,
voteCount: tv.last_episode_to_air.vote_count,
airDate: tv.last_episode_to_air.air_date ?? '',
episodeNumber: tv.last_episode_to_air.episode_number,
productionCode: tv.last_episode_to_air.production_code,
runtime: tv.last_episode_to_air.runtime ?? 0,
seasonNumber: tv.last_episode_to_air.season_number,
showId: tv.last_episode_to_air.show_id,
stillPath: tv.last_episode_to_air.still_path
} : null,
Name: tv.name,
Networks: tv.networks.map(network => ({
id: network.id,
logoPath: network.logo_path,
name: network.name,
originCountry: network.origin_country
})),
NumberOfEpisodes: tv.number_of_episodes,
NumberOfSeasons: tv.number_of_seasons,
OriginCountry: tv.origin_country,
OriginalLanguage: tv.original_language,
OriginalName: tv.original_name,
Overview: tv.overview,
Popularity: tv.popularity,
PosterPath: tv.poster_path ?? '',
ProductionCompanies: tv.production_companies.map(company => ({
id: company.id,
logoPath: company.logo_path,
name: company.name,
originCountry: company.origin_country
})),
ProductionCountries: tv.production_countries.map(country => ({
iso31661: country.iso_3166_1,
name: country.name
})),
Seasons: tv.seasons.map(season => ({
airDate: season.air_date,
episodeCount: season.episode_count,
id: season.id,
name: season.name,
overview: season.overview,
posterPath: season.poster_path,
seasonNumber: season.season_number,
voteAverage: season.vote_average
})),
SpokenLanguages: tv.spoken_languages.map(lang => ({
englishName: lang.english_name,
iso6391: lang.iso_639_1,
name: lang.name
})),
Status: tv.status,
Tagline: tv.tagline,
Type: tv.type,
VoteAverage: tv.vote_average,
VoteCount: tv.vote_count,
})

View File

@@ -21,15 +21,16 @@ async function searchMovie() {
isSearching.value = true
searchPage.value = 1
const result = await searchMovies(searchQuery.value)
if (!result.Response) {
console.log(result)
if (result.totalResults === 0) {
movies.value = []
seachError.value = result.ErrorMessage
seachError.value = 'No results found'
return
}
seachError.value = ''
movies.value = result.Search
movies.value = result.Results
state.setState(searchPage.value, searchQuery.value, result.Search)
state.setState(searchPage.value, searchQuery.value, result.Results)
} catch (error) {
movies.value = []
console.error(error)
@@ -44,7 +45,7 @@ async function loadMore() {
isLoadingMore.value = true
searchPage.value++
const result = await loadMoreMovies(searchQuery.value, searchPage.value)
movies.value?.push(...result.Search)
movies.value?.push(...result.Results)
state.setState(searchPage.value, searchQuery.value, movies.value ? movies.value : [])
} catch (error) {
searchPage.value = 1