Mise en place d'un model pour le fetch des crédits + interface.
Ajout fonction dans le composable useTMDB. Modification interface et model Movie pour ajouter la liaison avec Credit. Ajout du fetch des credit dans la page détails.
Showing
6 changed files
with
126 additions
and
51 deletions
1 | import type { RuntimeConfig } from "nuxt/schema"; | 1 | import type { RuntimeConfig } from "nuxt/schema"; |
2 | 2 | ||
3 | -export const useTMDB = function() { | 3 | +export const useTMDB = function () { |
4 | const runtimeconfig: RuntimeConfig = useRuntimeConfig(); | 4 | const runtimeconfig: RuntimeConfig = useRuntimeConfig(); |
5 | const apiUrl = runtimeconfig.public.apiTMDBUrl; | 5 | const apiUrl = runtimeconfig.public.apiTMDBUrl; |
6 | const apiKey = runtimeconfig.public.apiTMDBSecret; | 6 | const apiKey = runtimeconfig.public.apiTMDBSecret; |
@@ -11,9 +11,7 @@ export const useTMDB = function() { | @@ -11,9 +11,7 @@ export const useTMDB = function() { | ||
11 | */ | 11 | */ |
12 | const fetchPopularMovies = async (page: number) => { | 12 | const fetchPopularMovies = async (page: number) => { |
13 | try { | 13 | try { |
14 | - const response = await fetch( | 14 | + const response = await fetch(`${apiUrl}/movie/popular?api_key=${apiKey}&language=fr-FR&page=${page}`); |
15 | - `${apiUrl}/movie/popular?api_key=${apiKey}&language=fr-FR&page=${page}`, | ||
16 | - ); | ||
17 | if (!response.ok) { | 15 | if (!response.ok) { |
18 | console.error("An error occurred when fetching popular movies:"); | 16 | console.error("An error occurred when fetching popular movies:"); |
19 | } else { | 17 | } else { |
@@ -48,11 +46,9 @@ export const useTMDB = function() { | @@ -48,11 +46,9 @@ export const useTMDB = function() { | ||
48 | * Fetch movie details by id. | 46 | * Fetch movie details by id. |
49 | * @param id | 47 | * @param id |
50 | */ | 48 | */ |
51 | - const fetchMovieDetails = async (id: number|string) => { | 49 | + const fetchMovieDetails = async (id: number | string) => { |
52 | try { | 50 | try { |
53 | - const response = await fetch( | 51 | + const response = await fetch(`${apiUrl}/movie/${id}?api_key=${apiKey}&language=fr-FR`); |
54 | - `${apiUrl}/movie/${id}?api_key=${apiKey}&language=fr-FR`, | ||
55 | - ); | ||
56 | if (!response.ok) { | 52 | if (!response.ok) { |
57 | console.error("An error occurred when fetching movie details:"); | 53 | console.error("An error occurred when fetching movie details:"); |
58 | } else { | 54 | } else { |
@@ -63,5 +59,21 @@ export const useTMDB = function() { | @@ -63,5 +59,21 @@ export const useTMDB = function() { | ||
63 | } | 59 | } |
64 | }; | 60 | }; |
65 | 61 | ||
66 | - return { fetchPopularMovies, searchMovies, fetchMovieDetails } | 62 | + /** |
67 | -} | 63 | + * Fetch movie credits |
64 | + */ | ||
65 | + const fetchMovieCredits = async (id: number | string) => { | ||
66 | + try { | ||
67 | + const response = await fetch(`${apiUrl}/movie/${id}/credits?api_key=${apiKey}&language=fr-FR`); | ||
68 | + if (!response.ok) { | ||
69 | + console.error("An error occurred when fetching movie credits:"); | ||
70 | + } else { | ||
71 | + return await response.json(); | ||
72 | + } | ||
73 | + } catch (error) { | ||
74 | + console.error("Error fetching movie credits:", error); | ||
75 | + } | ||
76 | + }; | ||
77 | + | ||
78 | + return { fetchPopularMovies, searchMovies, fetchMovieDetails, fetchMovieCredits }; | ||
79 | +}; |
interfaces/credit.ts
0 → 100644
1 | +import type { MovieInterface } from "~/interfaces/movie"; | ||
2 | + | ||
3 | +export interface CreditInterface { | ||
4 | + id: number; | ||
5 | + name: string; | ||
6 | + job?: string; | ||
7 | + character?: string; | ||
8 | +} | ||
9 | + | ||
10 | +export type CreditsResponse = { | ||
11 | + id: number; | ||
12 | + cast: CreditInterface[], | ||
13 | + crew: CreditInterface[], | ||
14 | + movie_id: unknown; | ||
15 | + movie: MovieInterface; | ||
16 | +} |
1 | +import type { CreditInterface } from "~/interfaces/credit"; | ||
2 | + | ||
1 | export interface MovieInterface { | 3 | export interface MovieInterface { |
2 | id: number; | 4 | id: number; |
3 | adult: boolean; | 5 | adult: boolean; |
@@ -15,6 +17,7 @@ export interface MovieInterface { | @@ -15,6 +17,7 @@ export interface MovieInterface { | ||
15 | video: boolean; | 17 | video: boolean; |
16 | vote_average: number; | 18 | vote_average: number; |
17 | vote_count: number; | 19 | vote_count: number; |
20 | + credit: CreditInterface; | ||
18 | } | 21 | } |
19 | 22 | ||
20 | type Genre = { | 23 | type Genre = { |
models/credit.ts
0 → 100644
1 | +import { Model } from "pinia-orm"; | ||
2 | +import { Movie } from "~/models/movie"; | ||
3 | + | ||
4 | +export class Credit extends Model { | ||
5 | + /** | ||
6 | + * | ||
7 | + * @return {string} | ||
8 | + */ | ||
9 | + static get entity() { | ||
10 | + return "Credit"; | ||
11 | + } | ||
12 | + | ||
13 | + /** | ||
14 | + * | ||
15 | + * @return {string} | ||
16 | + */ | ||
17 | + static get primaryKey() { | ||
18 | + return "id"; | ||
19 | + } | ||
20 | + | ||
21 | + static fields() { | ||
22 | + return { | ||
23 | + // Attributs. | ||
24 | + id: this.number(null), | ||
25 | + cast: this.attr([]), | ||
26 | + crew: this.attr([]), | ||
27 | + // Relations. | ||
28 | + movie_id: this.attr(null), | ||
29 | + movie: this.belongsTo(Movie, "movie_id", "id"), | ||
30 | + }; | ||
31 | + } | ||
32 | + | ||
33 | + static piniaOptions = { | ||
34 | + persist: true, | ||
35 | + }; | ||
36 | +} |
1 | import { Model } from "pinia-orm"; | 1 | import { Model } from "pinia-orm"; |
2 | +import { Credit } from "~/models/credit"; | ||
2 | 3 | ||
3 | export class Movie extends Model { | 4 | export class Movie extends Model { |
4 | /** | 5 | /** |
@@ -48,11 +49,11 @@ export class Movie extends Model { | @@ -48,11 +49,11 @@ export class Movie extends Model { | ||
48 | vote_average: this.number(null), | 49 | vote_average: this.number(null), |
49 | vote_count: this.number(null), | 50 | vote_count: this.number(null), |
50 | // Relations. | 51 | // Relations. |
52 | + credit: this.hasOne(Credit, "movie_id", "id"), | ||
51 | }; | 53 | }; |
52 | } | 54 | } |
53 | 55 | ||
54 | static piniaOptions = { | 56 | static piniaOptions = { |
55 | persist: true, | 57 | persist: true, |
56 | }; | 58 | }; |
57 | - | 59 | +} |
58 | -} |
1 | -<script setup lang="ts"> | 1 | +<script lang="ts" setup> |
2 | //#region --import--. | 2 | //#region --import--. |
3 | import { ArrowLeftIcon, FilmIcon } from "lucide-vue-next"; | 3 | import { ArrowLeftIcon, FilmIcon } from "lucide-vue-next"; |
4 | import { useTMDB } from "~/composables/tMDB"; | 4 | import { useTMDB } from "~/composables/tMDB"; |
5 | -import { onMounted } from "vue"; | 5 | +import { onMounted, ref } from "vue"; |
6 | import { Movie } from "~/models/movie"; | 6 | import { Movie } from "~/models/movie"; |
7 | import type { MovieInterface } from "~/interfaces/movie"; | 7 | import type { MovieInterface } from "~/interfaces/movie"; |
8 | +import { Credit } from "~/models/credit"; | ||
9 | +import type { CreditInterface, CreditsResponse } from "~/interfaces/credit"; | ||
8 | //#endregion | 10 | //#endregion |
9 | 11 | ||
10 | //#region --Declaration--. | 12 | //#region --Declaration--. |
11 | -const { fetchPopularMovies, searchMovies, fetchMovieDetails } = useTMDB(); | 13 | +const { fetchMovieDetails, fetchMovieCredits } = useTMDB(); |
12 | //#endregion | 14 | //#endregion |
13 | 15 | ||
14 | //#region --Declaration--. | 16 | //#region --Declaration--. |
15 | const { currentRoute } = useRouter(); | 17 | const { currentRoute } = useRouter(); |
16 | //#endregion | 18 | //#endregion |
17 | 19 | ||
20 | +//#region --Data/ref--. | ||
21 | +const isLoading = ref(true); | ||
22 | +//#endregion | ||
23 | + | ||
18 | //#region --Computed--. | 24 | //#region --Computed--. |
19 | const movieId = computed(() => { | 25 | const movieId = computed(() => { |
20 | if (currentRoute.value.params.id) { | 26 | if (currentRoute.value.params.id) { |
21 | - if (typeof currentRoute.value.params.id === 'string') { | 27 | + if (typeof currentRoute.value.params.id === "string") { |
22 | if (typeof Number(+currentRoute.value.params.id) === "number") { | 28 | if (typeof Number(+currentRoute.value.params.id) === "number") { |
23 | return +currentRoute.value.params.id as number; | 29 | return +currentRoute.value.params.id as number; |
24 | } else { | 30 | } else { |
@@ -35,48 +41,44 @@ const movieId = computed(() => { | @@ -35,48 +41,44 @@ const movieId = computed(() => { | ||
35 | const movie = computed(() => { | 41 | const movie = computed(() => { |
36 | if (unref(movieId)) { | 42 | if (unref(movieId)) { |
37 | // Todo : revoir ici. | 43 | // Todo : revoir ici. |
38 | - return useRepo(Movie).query().where('id', movieId.value).withAll().first() as unknown as MovieInterface; | 44 | + return useRepo(Movie).query().where("id", movieId.value).withAll().first() as unknown as MovieInterface; |
39 | } else { | 45 | } else { |
40 | return null; | 46 | return null; |
41 | } | 47 | } |
42 | }); | 48 | }); |
43 | //#endregion | 49 | //#endregion |
44 | 50 | ||
45 | - | ||
46 | - | ||
47 | //#region --Function--. | 51 | //#region --Function--. |
48 | /** | 52 | /** |
49 | * Fetch movie details | 53 | * Fetch movie details |
50 | */ | 54 | */ |
51 | -const fetchDetails = async (id: number|string) => { | 55 | +const fetchDetails = async (id: number | string) => { |
52 | try { | 56 | try { |
53 | - // isLoading.value = true | 57 | + isLoading.value = true; |
54 | 58 | ||
55 | const data = await fetchMovieDetails(id); | 59 | const data = await fetchMovieDetails(id); |
56 | - console.log('data', data) | ||
57 | // Add to store collection. | 60 | // Add to store collection. |
58 | useRepo(Movie).save(data); | 61 | useRepo(Movie).save(data); |
59 | } catch (error) { | 62 | } catch (error) { |
60 | - console.error('Error fetching movie details:', error) | 63 | + console.error("Error fetching movie details:", error); |
61 | - // movie.value = null | ||
62 | } finally { | 64 | } finally { |
63 | - // isLoading.value = false | 65 | + isLoading.value = false; |
64 | } | 66 | } |
65 | -} | 67 | +}; |
66 | 68 | ||
67 | /** | 69 | /** |
68 | * Format runtime | 70 | * Format runtime |
69 | * @param minutes | 71 | * @param minutes |
70 | */ | 72 | */ |
71 | const formatRuntime = (minutes: number) => { | 73 | const formatRuntime = (minutes: number) => { |
72 | - if (!minutes) return 'Durée inconnue'; | 74 | + if (!minutes) return "Durée inconnue"; |
73 | // Find nb hours. | 75 | // Find nb hours. |
74 | const hours = Math.floor(minutes / 60); | 76 | const hours = Math.floor(minutes / 60); |
75 | // Find last minutes. | 77 | // Find last minutes. |
76 | const mins = minutes % 60; | 78 | const mins = minutes % 60; |
77 | 79 | ||
78 | return `${hours}h ${mins}min`; | 80 | return `${hours}h ${mins}min`; |
79 | -} | 81 | +}; |
80 | 82 | ||
81 | /** | 83 | /** |
82 | * Format vote count if > 1000. | 84 | * Format vote count if > 1000. |
@@ -84,21 +86,33 @@ const formatRuntime = (minutes: number) => { | @@ -84,21 +86,33 @@ const formatRuntime = (minutes: number) => { | ||
84 | */ | 86 | */ |
85 | const formatVoteCount = (count: number) => { | 87 | const formatVoteCount = (count: number) => { |
86 | if (count >= 1000) { | 88 | if (count >= 1000) { |
87 | - return `${(count / 1000).toFixed(1)}k votes` | 89 | + return `${(count / 1000).toFixed(1)}k votes`; |
90 | + } | ||
91 | + return `${count} votes`; | ||
92 | +}; | ||
93 | + | ||
94 | +async function fetchCredits(id: number|string) { | ||
95 | + try { | ||
96 | + const data = await fetchMovieCredits(id) as CreditsResponse; | ||
97 | + data.movie_id = id; | ||
98 | + // Add to store collection. | ||
99 | + console.log('credit response', data) | ||
100 | + | ||
101 | + useRepo(Credit).save(data); | ||
102 | + } catch (error) { | ||
103 | + console.error("Error fetching movie credits:", error); | ||
88 | } | 104 | } |
89 | - return `${count} votes` | ||
90 | } | 105 | } |
91 | //#endregion | 106 | //#endregion |
92 | 107 | ||
93 | - | ||
94 | //#region --Global event--. | 108 | //#region --Global event--. |
95 | onMounted(() => { | 109 | onMounted(() => { |
96 | // Fetch data on component mount. | 110 | // Fetch data on component mount. |
97 | if (unref(movieId)) { | 111 | if (unref(movieId)) { |
98 | - const id = unref(movieId) as string|number; | 112 | + const id = unref(movieId) as string | number; |
99 | - fetchDetails(id) | 113 | + fetchDetails(id); |
114 | + fetchCredits(id) | ||
100 | } | 115 | } |
101 | - // fetchMovieCredits() | ||
102 | // loadComments() | 116 | // loadComments() |
103 | }); | 117 | }); |
104 | //#endregion | 118 | //#endregion |
@@ -107,19 +121,19 @@ onMounted(() => { | @@ -107,19 +121,19 @@ onMounted(() => { | ||
107 | <template> | 121 | <template> |
108 | <section> | 122 | <section> |
109 | <!-- Skeleton loader pendant le chargement --> | 123 | <!-- Skeleton loader pendant le chargement --> |
110 | - <ui-components-skeleton-movie-detail-loader /> | 124 | + <ui-components-skeleton-movie-detail-loader v-if="isLoading" /> |
111 | 125 | ||
112 | <!-- Contenu du film --> | 126 | <!-- Contenu du film --> |
113 | - <div v-if="movie" class="relative"> | 127 | + <div v-else-if="movie" class="relative"> |
114 | <!-- Backdrop image --> | 128 | <!-- Backdrop image --> |
115 | <div class="absolute inset-0 h-[500px] overflow-hidden z-0"> | 129 | <div class="absolute inset-0 h-[500px] overflow-hidden z-0"> |
116 | - <div class="absolute inset-0 bg-gradient-to-b from-transparent to-gray-900"/> | 130 | + <div class="absolute inset-0 bg-gradient-to-b from-transparent to-gray-900" /> |
117 | <img | 131 | <img |
118 | v-if="movie.backdrop_path" | 132 | v-if="movie.backdrop_path" |
119 | - :src="`https://image.tmdb.org/t/p/original${movie.backdrop_path}`" | ||
120 | :alt="movie.title" | 133 | :alt="movie.title" |
134 | + :src="`https://image.tmdb.org/t/p/original${movie.backdrop_path}`" | ||
121 | class="w-full h-full object-cover opacity-30" | 135 | class="w-full h-full object-cover opacity-30" |
122 | - > | 136 | + /> |
123 | </div> | 137 | </div> |
124 | 138 | ||
125 | <!-- Contenu principal --> | 139 | <!-- Contenu principal --> |
@@ -138,10 +152,10 @@ onMounted(() => { | @@ -138,10 +152,10 @@ onMounted(() => { | ||
138 | <div class="rounded-lg overflow-hidden shadow-lg bg-gray-800"> | 152 | <div class="rounded-lg overflow-hidden shadow-lg bg-gray-800"> |
139 | <img | 153 | <img |
140 | v-if="movie.poster_path" | 154 | v-if="movie.poster_path" |
141 | - :src="`https://image.tmdb.org/t/p/w500${movie.poster_path}`" | ||
142 | :alt="movie.title" | 155 | :alt="movie.title" |
156 | + :src="`https://image.tmdb.org/t/p/w500${movie.poster_path}`" | ||
143 | class="w-full h-auto" | 157 | class="w-full h-auto" |
144 | - > | 158 | + /> |
145 | <div v-else class="aspect-[2/3] bg-gray-700 flex items-center justify-center"> | 159 | <div v-else class="aspect-[2/3] bg-gray-700 flex items-center justify-center"> |
146 | <FilmIcon :size="64" class="text-gray-500" /> | 160 | <FilmIcon :size="64" class="text-gray-500" /> |
147 | </div> | 161 | </div> |
@@ -169,11 +183,7 @@ onMounted(() => { | @@ -169,11 +183,7 @@ onMounted(() => { | ||
169 | <!-- Genres --> | 183 | <!-- Genres --> |
170 | <div class="mb-6"> | 184 | <div class="mb-6"> |
171 | <div class="flex flex-wrap gap-2"> | 185 | <div class="flex flex-wrap gap-2"> |
172 | - <span | 186 | + <span v-for="genre in movie.genres" :key="genre.id" class="px-3 py-1 bg-gray-800 rounded-full text-sm"> |
173 | - v-for="genre in movie.genres" | ||
174 | - :key="genre.id" | ||
175 | - class="px-3 py-1 bg-gray-800 rounded-full text-sm" | ||
176 | - > | ||
177 | {{ genre.name }} | 187 | {{ genre.name }} |
178 | </span> | 188 | </span> |
179 | </div> | 189 | </div> |
@@ -182,9 +192,8 @@ onMounted(() => { | @@ -182,9 +192,8 @@ onMounted(() => { | ||
182 | <!-- Synopsis --> | 192 | <!-- Synopsis --> |
183 | <div class="mb-6"> | 193 | <div class="mb-6"> |
184 | <h2 class="text-xl font-bold mb-2">Synopsis</h2> | 194 | <h2 class="text-xl font-bold mb-2">Synopsis</h2> |
185 | - <p class="text-gray-300">{{ movie.overview || 'Aucun synopsis disponible.' }}</p> | 195 | + <p class="text-gray-300">{{ movie.overview || "Aucun synopsis disponible." }}</p> |
186 | </div> | 196 | </div> |
187 | - | ||
188 | </div> | 197 | </div> |
189 | </div> | 198 | </div> |
190 | </div> | 199 | </div> |
@@ -192,6 +201,4 @@ onMounted(() => { | @@ -192,6 +201,4 @@ onMounted(() => { | ||
192 | </section> | 201 | </section> |
193 | </template> | 202 | </template> |
194 | 203 | ||
195 | -<style scoped> | 204 | +<style scoped></style> |
196 | - | ||
197 | -</style> |
-
Please register or login to post a comment