Toggle navigation
Toggle navigation
This project
Loading...
Sign in
Bruno Predot
/
tmdb_test
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Authored by
Bruno Predot
2025-05-17 10:43:34 +0200
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
5a53f1456886b343092fe7dadc35e69ed5fb8346
5a53f145
1 parent
545aac47
lintfix
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
394 additions
and
97 deletions
.eslintrc.cjs
components/MovieCard.vue
components/MovieCommentList.vue
components/MoviesList.vue
components/details/MovieGender.vue
components/details/ScoreAndVote.vue
components/form/MovieCommentForm.vue
components/ui-components/Loader.vue
components/ui-components/Poster.vue
components/ui-components/SearchBar.vue
components/ui-components/SkeletonMoviesLoader.vue
components/ui-components/TinyMceFieldEditor.vue
components/ui-components/skeletonMovieDetailLoader.vue
nuxt.config.ts
package-lock.json
package.json
pages/movies/[id]/index.vue
.eslintrc.cjs
View file @
5a53f14
module.exports = {
// https://dev.to/tao/adding-eslint-and-prettier-to-nuxt-3-2023-5bg
root: true,
extends: ["@nuxtjs/eslint-config", "plugin:prettier/recommended"],
env: {
browser: true,
node: true,
},
parser: "vue-eslint-parser",
parserOptions: {
parser: "@typescript-eslint/parser",
},
plugins: [],
// add your custom rules here
rules: {},
};
...
...
components/MovieCard.vue
View file @
5a53f14
...
...
@@ -2,16 +2,22 @@
//#region --Props--.
import { useDateFormat } from "@vueuse/core";
import { FilmIcon } from "lucide-vue-next";
import type { MovieInterface } from "~/interfaces/movie";
//#endregion
//#region --Props--.
defineProps({
movie: {
type: Object,
required: true,
nullable: false,
},
});
/** Typescript typage */
defineProps<{
movie: MovieInterface;
}>();
/** Ancien typage */
// defineProps({
// movie: {
// type: Object,
// required: true,
// nullable: false,
// },
// });
//#endregion
</script>
...
...
@@ -26,9 +32,15 @@ defineProps({
:alt="movie.title"
:src="`https://image.tmdb.org/t/p/w500${movie.poster_path}`"
class="absolute inset-0 w-full h-full object-cover"
/>
<div v-else class="absolute inset-0 w-full h-full bg-gray-700 flex items-center justify-center">
<FilmIcon :size="48" class="text-gray-500" />
>
<div
v-else
class="absolute inset-0 w-full h-full bg-gray-700 flex items-center justify-center"
>
<FilmIcon
:size="48"
class="text-gray-500"
/>
</div>
<div
class="absolute top-2 right-2 bg-primary text-white rounded-full w-10 h-10 flex items-center justify-center font-bold"
...
...
components/MovieCommentList.vue
View file @
5a53f14
...
...
@@ -5,13 +5,18 @@ import { MessageSquareIcon } from "lucide-vue-next";
//#endregion
//#region --Props--.
const props = defineProps({
comments: {
type: Array<MovieCommentInterface>,
required: true,
nullable: false,
},
});
/** Typescript typage */
const props = defineProps<{
comments: Array<MovieCommentInterface>;
}>();
/** Ancien typage */
// const props = defineProps({
// comments: {
// type: Array<MovieCommentInterface>,
// required: true,
// nullable: false,
// },
// });
//#endregion
//#region --Watch--.
...
...
@@ -35,27 +40,49 @@ watch(
<template>
<section>
<!-- Liste des commentaires -->
<section v-if="comments.length > 0" class="mt-10">
<section
v-if="comments.length > 0"
class="mt-10"
>
<h2>Commentaires publiés</h2>
<div v-for="(comment, index) in comments" :key="index" class="bg-gray-800 rounded-lg p-6 mb-4">
<div
v-for="(comment, index) in comments"
:key="index"
class="bg-gray-800 rounded-lg p-6 mb-4"
>
<div class="flex justify-between items-start mb-2">
<section>
<h4 class="font-bold text-lg">Par {{ comment.username }}</h4>
<p class="text-sm text-gray-400">Le {{ useDateFormat(comment.createdAt, "DD-MM-YYYY") }}</p>
<h4 class="font-bold text-lg">
Par {{ comment.username }}
</h4>
<p class="text-sm text-gray-400">
Le {{ useDateFormat(comment.createdAt, "DD-MM-YYYY") }}
</p>
</section>
<section class="bg-primary text-white rounded-full w-10 h-10 flex items-center justify-center font-bold">
{{ comment.rating }}
</section>
</div>
<p :id="`message${index}`" class="text-gray-300">
<p
:id="`message${index}`"
class="text-gray-300"
>
{{ comment.message }}
</p>
</div>
</section>
<!-- Si aucun commentaire -->
<section v-else class="text-center py-8 bg-gray-800 rounded-lg mt-10">
<MessageSquareIcon :size="48" class="mx-auto mb-3 text-gray-600" />
<p class="text-gray-400">Aucun commentaire pour le moment. Soyez le premier à donner votre avis !</p>
<section
v-else
class="text-center py-8 bg-gray-800 rounded-lg mt-10"
>
<MessageSquareIcon
:size="48"
class="mx-auto mb-3 text-gray-600"
/>
<p class="text-gray-400">
Aucun commentaire pour le moment. Soyez le premier à donner votre avis !
</p>
</section>
</section>
</template>
...
...
components/MoviesList.vue
View file @
5a53f14
...
...
@@ -151,7 +151,9 @@ onBeforeUnmount(() => {
<template>
<section>
<h1 class="text-4xl font-bold mb-8 text-center">Découvrez les films populaires</h1>
<h1 class="text-4xl font-bold mb-8 text-center">
Découvrez les films populaires
</h1>
<!-- Barre de recherche -->
<ui-components-search-bar
placeholder="Rechercher un film..."
...
...
@@ -167,24 +169,46 @@ onBeforeUnmount(() => {
/>
<!-- Liste des films -->
<div v-else-if="movies.length > 0" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
<div v-for="movie in movies" :key="movie.id">
<div
v-else-if="movies.length > 0"
class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"
>
<div
v-for="movie in movies"
:key="movie.id"
>
<movie-card :movie="movie" />
</div>
</div>
<!-- Message si aucun film trouvé -->
<section v-else-if="searchQuery && !movies.length" class="text-center py-12">
<SearchXIcon :size="64" class="mx-auto mb-4 text-gray-600" />
<h3 class="text-xl font-bold mb-2">Aucun film trouvé</h3>
<p class="text-gray-400">Essayez avec un autre terme de recherche</p>
<section
v-else-if="searchQuery && !movies.length"
class="text-center py-12"
>
<SearchXIcon
:size="64"
class="mx-auto mb-4 text-gray-600"
/>
<h3 class="text-xl font-bold mb-2">
Aucun film trouvé
</h3>
<p class="text-gray-400">
Essayez avec un autre terme de recherche
</p>
</section>
<!-- Loader pour le chargement de plus de films -->
<ui-components-loader :is-initial-loading="isInitialLoading" :is-loading="isLoadingMore" />
<ui-components-loader
:is-initial-loading="isInitialLoading"
:is-loading="isLoadingMore"
/>
<!-- Élément observé pour le défilement infini -->
<div ref="loadMoreTrigger" class="h-10 mt-4" />
<div
ref="loadMoreTrigger"
class="h-10 mt-4"
/>
</section>
</template>
...
...
components/details/MovieGender.vue
View file @
5a53f14
...
...
@@ -17,7 +17,11 @@ defineProps({
<template>
<section class="mb-6">
<div class="flex flex-wrap gap-2">
<span v-for="genre in genres" :key="genre.id" class="px-3 py-1 bg-gray-800 rounded-full text-sm">
<span
v-for="genre in genres"
:key="genre.id"
class="px-3 py-1 bg-gray-800 rounded-full text-sm"
>
{{ genre.name }}
</span>
</div>
...
...
components/details/ScoreAndVote.vue
View file @
5a53f14
<script lang="ts" setup>
//#region --Props--.
defineProps({
score: {
type: Number,
required: true,
nullable: false,
},
nbVote: {
type: Number,
required: true,
nullable: false,
},
});
/** Typescript typage */
defineProps<{
score: number;
nbVote: number;
}>();
/** Ancien typage */
// defineProps({
// score: {
// type: Number,
// required: true,
// nullable: false,
// },
// nbVote: {
// type: Number,
// required: true,
// nullable: false,
// },
// });
//#endregion
//#region --Function--.
...
...
@@ -34,7 +40,9 @@ const formatVoteCount = (count: number) => {
{{ score.toFixed(1) }}
</section>
<section>
<p class="font-semibold">Note TMDB</p>
<p class="font-semibold">
Note TMDB
</p>
<div class="text-sm text-gray-400">
{{ formatVoteCount(nbVote) }}
</div>
...
...
components/form/MovieCommentForm.vue
View file @
5a53f14
...
...
@@ -123,13 +123,22 @@ function handleMessageEvent(event: string) {
}
"
>
<span v-if="isSubmitting" class="flex items-center justify-center">
<span
v-if="isSubmitting"
class="flex items-center justify-center"
>
<span class="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin mr-2" />
Envoi en cours...
</span>
<span v-else>Publier le commentaire</span>
</v-btn>
<v-btn class="mt-6 mr-4" color="primary" @click="clear"> effacer </v-btn>
<v-btn
class="mt-6 mr-4"
color="primary"
@click="clear"
>
effacer
</v-btn>
</VForm>
</section>
</template>
...
...
components/ui-components/Loader.vue
View file @
5a53f14
...
...
@@ -17,7 +17,10 @@ defineProps({
</script>
<template>
<section v-if="isLoading && !isInitialLoading" class="flex justify-center mt-8">
<section
v-if="isLoading && !isInitialLoading"
class="flex justify-center mt-8"
>
<div class="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin" />
</section>
</template>
...
...
components/ui-components/Poster.vue
View file @
5a53f14
...
...
@@ -20,9 +20,20 @@ defineProps({
<template>
<section class="w-full md:w-1/3 lg:w-1/4">
<div class="rounded-lg overflow-hidden shadow-lg bg-gray-800">
<v-img v-if="src" :alt="title" :src="`https://image.tmdb.org/t/p/w500${src}`" class="w-full h-auto" />
<div v-else class="aspect-[2/3] bg-gray-700 flex items-center justify-center">
<FilmIcon :size="64" class="text-gray-500" />
<v-img
v-if="src"
:alt="title"
:src="`https://image.tmdb.org/t/p/w500${src}`"
class="w-full h-auto"
/>
<div
v-else
class="aspect-[2/3] bg-gray-700 flex items-center justify-center"
>
<FilmIcon
:size="64"
class="text-gray-500"
/>
</div>
</div>
</section>
...
...
components/ui-components/SearchBar.vue
View file @
5a53f14
...
...
@@ -49,7 +49,7 @@ function handleClearSearchEvent() {
class="w-full px-4 py-3 bg-gray-800 rounded-full text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary"
type="text"
@input="handleSearchEvent"
/
>
>
<button
v-if="searchQuery"
class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-white"
...
...
@@ -57,7 +57,10 @@ function handleClearSearchEvent() {
>
<XIcon :size="20" />
</button>
<button v-else class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400">
<button
v-else
class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400"
>
<SearchIcon :size="20" />
</button>
</div>
...
...
components/ui-components/SkeletonMoviesLoader.vue
View file @
5a53f14
...
...
@@ -18,8 +18,15 @@ defineProps({
<template>
<!-- Skeleton loader pendant le chargement initial -->
<section v-if="isInitialLoading" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
<div v-for="i in skeletonNumber" :key="i" class="bg-gray-800 rounded-lg overflow-hidden shadow-lg animate-pulse">
<section
v-if="isInitialLoading"
class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"
>
<div
v-for="i in skeletonNumber"
:key="i"
class="bg-gray-800 rounded-lg overflow-hidden shadow-lg animate-pulse"
>
<div class="h-80 bg-gray-700" />
<div class="p-4">
<div class="h-6 bg-gray-700 rounded mb-3" />
...
...
components/ui-components/TinyMceFieldEditor.vue
View file @
5a53f14
...
...
@@ -79,9 +79,16 @@ watch(
<template>
<div>
<editor v-model="content" :api-key="runtimeConfig.public.apiTinyMceSecret" :init="init" />
<editor
v-model="content"
:api-key="runtimeConfig.public.apiTinyMceSecret"
:init="init"
/>
</div>
<div v-if="errorMessage" class="text-red-500 text-sm mt-1">
<div
v-if="errorMessage"
class="text-red-500 text-sm mt-1"
>
{{ errorMessage }}
</div>
</template>
...
...
components/ui-components/skeletonMovieDetailLoader.vue
View file @
5a53f14
...
...
@@ -3,7 +3,10 @@
<template>
<v-container class="bg-gray-900">
<v-row class="bg-gray-900">
<v-col cols="12" sm="4">
<v-col
cols="12"
sm="4"
>
<v-skeleton-loader
class="mx-auto border bg-gray-800"
color="#1f2937"
...
...
@@ -12,7 +15,10 @@
type="paragraph, image"
/>
</v-col>
<v-col cols="12" sm="8">
<v-col
cols="12"
sm="8"
>
<v-skeleton-loader
class="mx-auto mt-10"
color="#1f2937"
...
...
nuxt.config.ts
View file @
5a53f14
...
...
@@ -21,6 +21,12 @@ export default defineNuxtConfig({
// css: ['~/assets/css/main.scss'],
eslint
:
{
config
:
{
stylistic
:
true
,
},
},
modules
:
[
"@nuxt/eslint"
,
"@nuxt/icon"
,
...
...
package-lock.json
View file @
5a53f14
...
...
@@ -22,8 +22,8 @@
"@vitejs/plugin-vue"
:
"^5.2.3"
,
"@vuelidate/core"
:
"^2.0.3"
,
"@vuelidate/validators"
:
"^2.0.4"
,
"@vueuse/core"
:
"^13.
1
.0"
,
"@vueuse/nuxt"
:
"^13.
1
.0"
,
"@vueuse/core"
:
"^13.
2
.0"
,
"@vueuse/nuxt"
:
"^13.
2
.0"
,
"eslint"
:
"^9.25.1"
,
"lucide-vue-next"
:
"^0.503.0"
,
"nuxt"
:
"^3.16.2"
,
...
...
@@ -36,6 +36,7 @@
"devDependencies"
:
{
"@nuxt/test-utils"
:
"^3.17.2"
,
"@nuxtjs/tailwindcss"
:
"^6.13.2"
,
"@typescript-eslint/parser"
:
"^8.32.1"
,
"@vue/test-utils"
:
"^2.4.6"
,
"eslint-config-prettier"
:
"^10.1.2"
,
"eslint-plugin-prettier"
:
"^5.2.6"
,
...
...
@@ -44,7 +45,8 @@
"playwright-core"
:
"^1.52.0"
,
"prettier"
:
"^3.5.3"
,
"typescript-eslint"
:
"^8.32.1"
,
"vitest"
:
"^3.1.2"
"vitest"
:
"^3.1.2"
,
"vue-eslint-parser"
:
"^10.1.3"
}
},
"node_modules/@alloc/quick-lru"
:
{
...
...
@@ -6212,14 +6214,14 @@
}
},
"node_modules/@vueuse/core"
:
{
"version"
:
"13.
1
.0"
,
"resolved"
:
"https://registry.npmjs.org/@vueuse/core/-/core-13.
1
.0.tgz"
,
"integrity"
:
"sha512-
PAauvdRXZvTWXtGLg8cPUFjiZEddTqmogdwYpnn60t08AA5a8Q4hZokBnpTOnVNqySlFlTcRYIC8OqreV4hv3Q
=="
,
"version"
:
"13.
2
.0"
,
"resolved"
:
"https://registry.npmjs.org/@vueuse/core/-/core-13.
2
.0.tgz"
,
"integrity"
:
"sha512-
n5TZoIAxbWAQ3PqdVPDzLgIRQOujFfMlatdI+f7ditSmoEeNpPBvp7h2zamzikCmrhFIePAwdEQB6ENccHr7Rg
=="
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@types/web-bluetooth"
:
"^0.0.21"
,
"@vueuse/metadata"
:
"13.
1
.0"
,
"@vueuse/shared"
:
"13.
1
.0"
"@vueuse/metadata"
:
"13.
2
.0"
,
"@vueuse/shared"
:
"13.
2
.0"
},
"funding"
:
{
"url"
:
"https://github.com/sponsors/antfu"
...
...
@@ -6228,6 +6230,18 @@
"vue"
:
"^3.5.0"
}
},
"node_modules/@vueuse/core/node_modules/@vueuse/shared"
:
{
"version"
:
"13.2.0"
,
"resolved"
:
"https://registry.npmjs.org/@vueuse/shared/-/shared-13.2.0.tgz"
,
"integrity"
:
"sha512-vx9ZPDF5HcU9up3Jgt3G62dMUfZEdk6tLyBAHYAG4F4n73vpaA7J5hdncDI/lS9Vm7GA/FPlbOmh9TrDZROTpg=="
,
"license"
:
"MIT"
,
"funding"
:
{
"url"
:
"https://github.com/sponsors/antfu"
},
"peerDependencies"
:
{
"vue"
:
"^3.5.0"
}
},
"node_modules/@vueuse/integrations"
:
{
"version"
:
"13.1.0"
,
"resolved"
:
"https://registry.npmjs.org/@vueuse/integrations/-/integrations-13.1.0.tgz"
,
...
...
@@ -6294,6 +6308,32 @@
}
}
},
"node_modules/@vueuse/integrations/node_modules/@vueuse/core"
:
{
"version"
:
"13.1.0"
,
"resolved"
:
"https://registry.npmjs.org/@vueuse/core/-/core-13.1.0.tgz"
,
"integrity"
:
"sha512-PAauvdRXZvTWXtGLg8cPUFjiZEddTqmogdwYpnn60t08AA5a8Q4hZokBnpTOnVNqySlFlTcRYIC8OqreV4hv3Q=="
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@types/web-bluetooth"
:
"^0.0.21"
,
"@vueuse/metadata"
:
"13.1.0"
,
"@vueuse/shared"
:
"13.1.0"
},
"funding"
:
{
"url"
:
"https://github.com/sponsors/antfu"
},
"peerDependencies"
:
{
"vue"
:
"^3.5.0"
}
},
"node_modules/@vueuse/integrations/node_modules/@vueuse/metadata"
:
{
"version"
:
"13.1.0"
,
"resolved"
:
"https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.1.0.tgz"
,
"integrity"
:
"sha512-+TDd7/a78jale5YbHX9KHW3cEDav1lz1JptwDvep2zSG8XjCsVE+9mHIzjTOaPbHUAk5XiE4jXLz51/tS+aKQw=="
,
"license"
:
"MIT"
,
"funding"
:
{
"url"
:
"https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/math"
:
{
"version"
:
"13.1.0"
,
"resolved"
:
"https://registry.npmjs.org/@vueuse/math/-/math-13.1.0.tgz"
,
...
...
@@ -6310,23 +6350,23 @@
}
},
"node_modules/@vueuse/metadata"
:
{
"version"
:
"13.
1
.0"
,
"resolved"
:
"https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.
1
.0.tgz"
,
"integrity"
:
"sha512-
+TDd7/a78jale5YbHX9KHW3cEDav1lz1JptwDvep2zSG8XjCsVE+9mHIzjTOaPbHUAk5XiE4jXLz51/tS+aKQw
=="
,
"version"
:
"13.
2
.0"
,
"resolved"
:
"https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.
2
.0.tgz"
,
"integrity"
:
"sha512-
kPpzuQCU0+D8DZCzK0iPpIcXI+6ufWSgwnjJ6//GNpEn+SHViaCtR+XurzORChSgvpHO9YC8gGM97Y1kB+UabA
=="
,
"license"
:
"MIT"
,
"funding"
:
{
"url"
:
"https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/nuxt"
:
{
"version"
:
"13.
1
.0"
,
"resolved"
:
"https://registry.npmjs.org/@vueuse/nuxt/-/nuxt-13.
1
.0.tgz"
,
"integrity"
:
"sha512-
4xdxwKanLY4+z+/ZgSZcJvwuHlgZMU3km7z4lhlbLl6WZTKS3BiztnRzcrdt4zjU512oTlH5nsPNhUhV0KXiO
A=="
,
"version"
:
"13.
2
.0"
,
"resolved"
:
"https://registry.npmjs.org/@vueuse/nuxt/-/nuxt-13.
2
.0.tgz"
,
"integrity"
:
"sha512-
vLEyR2njEpugSOi12SuWwrClFxXrG/X20XExvkdHhIZ2R5qhm3wbmThUmHZ9K8AJI1+Y1m/qJUzmrac2FtLre
A=="
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@nuxt/kit"
:
"^3.1
6.2
"
,
"@vueuse/core"
:
"13.
1
.0"
,
"@vueuse/metadata"
:
"13.
1
.0"
,
"@nuxt/kit"
:
"^3.1
7.3
"
,
"@vueuse/core"
:
"13.
2
.0"
,
"@vueuse/metadata"
:
"13.
2
.0"
,
"local-pkg"
:
"^1.1.1"
},
"funding"
:
{
...
...
@@ -6337,6 +6377,80 @@
"vue"
:
"^3.5.0"
}
},
"node_modules/@vueuse/nuxt/node_modules/@nuxt/kit"
:
{
"version"
:
"3.17.3"
,
"resolved"
:
"https://registry.npmjs.org/@nuxt/kit/-/kit-3.17.3.tgz"
,
"integrity"
:
"sha512-aw6u6mT3TnM/MmcCRDMv3i9Sbm5/ZMSJgDl+N+WsrWNDIQ2sWmsqdDkjb/HyXF20SNwc2891hRBkaQr3hG2mhA=="
,
"license"
:
"MIT"
,
"dependencies"
:
{
"c12"
:
"^3.0.3"
,
"consola"
:
"^3.4.2"
,
"defu"
:
"^6.1.4"
,
"destr"
:
"^2.0.5"
,
"errx"
:
"^0.1.0"
,
"exsolve"
:
"^1.0.5"
,
"ignore"
:
"^7.0.4"
,
"jiti"
:
"^2.4.2"
,
"klona"
:
"^2.0.6"
,
"knitwork"
:
"^1.2.0"
,
"mlly"
:
"^1.7.4"
,
"ohash"
:
"^2.0.11"
,
"pathe"
:
"^2.0.3"
,
"pkg-types"
:
"^2.1.0"
,
"scule"
:
"^1.3.0"
,
"semver"
:
"^7.7.1"
,
"std-env"
:
"^3.9.0"
,
"tinyglobby"
:
"^0.2.13"
,
"ufo"
:
"^1.6.1"
,
"unctx"
:
"^2.4.1"
,
"unimport"
:
"^5.0.1"
,
"untyped"
:
"^2.0.0"
},
"engines"
:
{
"node"
:
">=18.12.0"
}
},
"node_modules/@vueuse/nuxt/node_modules/tinyglobby"
:
{
"version"
:
"0.2.13"
,
"resolved"
:
"https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz"
,
"integrity"
:
"sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw=="
,
"license"
:
"MIT"
,
"dependencies"
:
{
"fdir"
:
"^6.4.4"
,
"picomatch"
:
"^4.0.2"
},
"engines"
:
{
"node"
:
">=12.0.0"
},
"funding"
:
{
"url"
:
"https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/@vueuse/nuxt/node_modules/unimport"
:
{
"version"
:
"5.0.1"
,
"resolved"
:
"https://registry.npmjs.org/unimport/-/unimport-5.0.1.tgz"
,
"integrity"
:
"sha512-1YWzPj6wYhtwHE+9LxRlyqP4DiRrhGfJxdtH475im8ktyZXO3jHj/3PZ97zDdvkYoovFdi0K4SKl3a7l92v3sQ=="
,
"license"
:
"MIT"
,
"dependencies"
:
{
"acorn"
:
"^8.14.1"
,
"escape-string-regexp"
:
"^5.0.0"
,
"estree-walker"
:
"^3.0.3"
,
"local-pkg"
:
"^1.1.1"
,
"magic-string"
:
"^0.30.17"
,
"mlly"
:
"^1.7.4"
,
"pathe"
:
"^2.0.3"
,
"picomatch"
:
"^4.0.2"
,
"pkg-types"
:
"^2.1.0"
,
"scule"
:
"^1.3.0"
,
"strip-literal"
:
"^3.0.0"
,
"tinyglobby"
:
"^0.2.13"
,
"unplugin"
:
"^2.3.2"
,
"unplugin-utils"
:
"^0.2.4"
},
"engines"
:
{
"node"
:
">=18.12.0"
}
},
"node_modules/@vueuse/shared"
:
{
"version"
:
"13.1.0"
,
"resolved"
:
"https://registry.npmjs.org/@vueuse/shared/-/shared-13.1.0.tgz"
,
...
...
package.json
View file @
5a53f14
...
...
@@ -30,8 +30,8 @@
"@vitejs/plugin-vue"
:
"^5.2.3"
,
"@vuelidate/core"
:
"^2.0.3"
,
"@vuelidate/validators"
:
"^2.0.4"
,
"@vueuse/core"
:
"^13.
1
.0"
,
"@vueuse/nuxt"
:
"^13.
1
.0"
,
"@vueuse/core"
:
"^13.
2
.0"
,
"@vueuse/nuxt"
:
"^13.
2
.0"
,
"eslint"
:
"^9.25.1"
,
"lucide-vue-next"
:
"^0.503.0"
,
"nuxt"
:
"^3.16.2"
,
...
...
@@ -44,6 +44,7 @@
"devDependencies"
:
{
"@nuxt/test-utils"
:
"^3.17.2"
,
"@nuxtjs/tailwindcss"
:
"^6.13.2"
,
"@typescript-eslint/parser"
:
"^8.32.1"
,
"@vue/test-utils"
:
"^2.4.6"
,
"eslint-config-prettier"
:
"^10.1.2"
,
"eslint-plugin-prettier"
:
"^5.2.6"
,
...
...
@@ -52,6 +53,7 @@
"playwright-core"
:
"^1.52.0"
,
"prettier"
:
"^3.5.3"
,
"typescript-eslint"
:
"^8.32.1"
,
"vitest"
:
"^3.1.2"
"vitest"
:
"^3.1.2"
,
"vue-eslint-parser"
:
"^10.1.3"
}
}
...
...
pages/movies/[id]/index.vue
View file @
5a53f14
...
...
@@ -153,9 +153,16 @@ onMounted(() => {
<ui-components-skeleton-movie-detail-loader v-if="isLoading" />
<!-- Contenu du film -->
<div v-else-if="movie" class="relative">
<div
v-else-if="movie"
class="relative"
>
<!-- Backdrop image -->
<ui-components-backdrop-image v-if="movie.backdrop_path" :src="movie.backdrop_path" :title="movie.title" />
<ui-components-backdrop-image
v-if="movie.backdrop_path"
:src="movie.backdrop_path"
:title="movie.title"
/>
<!-- Contenu principal -->
<div class="container mx-auto px-4 py-8 relative z-10 pt-20">
...
...
@@ -163,41 +170,64 @@ onMounted(() => {
class="flex items-center text-gray-400 hover:text-white mb-8 transition-colors"
@click="navigateTo('/')"
>
<ArrowLeftIcon :size="20" class="mr-2" />
<ArrowLeftIcon
:size="20"
class="mr-2"
/>
Retour
</button>
<div class="flex flex-col md:flex-row gap-8">
<!-- Poster -->
<ui-components-poster v-if="movie.poster_path" :src="movie.poster_path" :title="movie.title" />
<ui-components-poster
v-if="movie.poster_path"
:src="movie.poster_path"
:title="movie.title"
/>
<!-- Informations du film -->
<section class="w-full md:w-2/3 lg:w-3/4">
<h1 class="text-3xl md:text-4xl font-bold mb-2">
{{ movie.title }}
</h1>
<p v-if="movie.release_date" class="text-gray-400 mb-4">
<p
v-if="movie.release_date"
class="text-gray-400 mb-4"
>
{{ useDateFormat(movie.release_date, "DD-MM-YYYY") }} • {{ formatRuntime(movie.runtime) }}
</p>
<!-- Note et votes -->
<details-score-and-vote :nb-vote="movie.vote_count" :score="movie.vote_average" />
<details-score-and-vote
:nb-vote="movie.vote_count"
:score="movie.vote_average"
/>
<!-- Genres -->
<details-movie-gender :genres="movie.genres" />
<!-- Synopsis -->
<div class="mb-6">
<h2 class="text-xl font-bold mb-2">Synopsis</h2>
<h2 class="text-xl font-bold mb-2">
Synopsis
</h2>
<p class="text-gray-300">
{{ movie.overview || "Aucun synopsis disponible." }}
</p>
</div>
<!-- Réalisateur et têtes d'affiche -->
<div v-if="movie.credit" class="mb-6">
<h2 class="text-xl font-bold mb-2">Équipe</h2>
<div v-if="director" class="mb-2">
<div
v-if="movie.credit"
class="mb-6"
>
<h2 class="text-xl font-bold mb-2">
Équipe
</h2>
<div
v-if="director"
class="mb-2"
>
<span class="font-semibold">Réalisateur:</span> {{ director.name }}
</div>
<div v-if="movie.credit.cast.length > 0">
...
...
@@ -212,7 +242,9 @@ onMounted(() => {
</div>
</div>
<!-- Comments form. -->
<h3 class="text-xl font-bold mt-8 mb-4">Ajouter un commentaire</h3>
<h3 class="text-xl font-bold mt-8 mb-4">
Ajouter un commentaire
</h3>
<form-movie-comment-form @event:submit="handleSubmitEvent" />
<!-- Liste des commentaires -->
...
...
@@ -223,10 +255,20 @@ onMounted(() => {
</div>
<!-- Erreur -->
<section v-else class="container mx-auto px-4 py-16 text-center">
<AlertTriangleIcon :size="64" class="mx-auto mb-4 text-red-500" />
<h2 class="text-2xl font-bold mb-2">Film non trouvé</h2>
<p class="text-gray-400 mb-6">Nous n'avons pas pu trouver le film que vous cherchez.</p>
<section
v-else
class="container mx-auto px-4 py-16 text-center"
>
<AlertTriangleIcon
:size="64"
class="mx-auto mb-4 text-red-500"
/>
<h2 class="text-2xl font-bold mb-2">
Film non trouvé
</h2>
<p class="text-gray-400 mb-6">
Nous n'avons pas pu trouver le film que vous cherchez.
</p>
<button
class="px-6 py-2 bg-primary text-white font-bold rounded-md hover:bg-primary-dark transition-colors"
@click="navigateTo('/')"
...
...
Please
register
or
login
to post a comment