From a96acf2a8a6eac1dd27baa9170f43d19bb3490fc Mon Sep 17 00:00:00 2001 From: ABHI N <abhin@ABHIs-Laptop.local> Date: Fri, 4 Apr 2025 12:50:55 +0100 Subject: [PATCH] Add TopRatedMovies feature --- movie-group-8/.env | 1 + movie-group-8/src/components/Navbar.vue | 2 + .../src/components/TopRatedMovies.vue | 140 ++++++++++++++++++ movie-group-8/src/router/index.js | 5 + movie-group-8/src/views/Films.vue | 9 +- movie-group-8/src/views/TopRated.vue | 44 ++++++ movie-group-8/vite.config.js | 8 + 7 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 movie-group-8/.env create mode 100644 movie-group-8/src/components/TopRatedMovies.vue create mode 100644 movie-group-8/src/views/TopRated.vue diff --git a/movie-group-8/.env b/movie-group-8/.env new file mode 100644 index 0000000..e847065 --- /dev/null +++ b/movie-group-8/.env @@ -0,0 +1 @@ +VITE_TMDB_API_KEY=42259df77843511296d8096fa29e08a8 diff --git a/movie-group-8/src/components/Navbar.vue b/movie-group-8/src/components/Navbar.vue index 5ae0401..7abd7a8 100644 --- a/movie-group-8/src/components/Navbar.vue +++ b/movie-group-8/src/components/Navbar.vue @@ -86,6 +86,8 @@ const userPhotoURL = ref(''); const navigation = ref([ { name: 'Films', href: '/films', authRequired: true }, { name: 'Watchlist', href: '/watchlist', authRequired: true }, + { name: 'Top Rated', href: '/top-rated', authRequired: true }, + ]); // Compute the current route to match against the navigation links diff --git a/movie-group-8/src/components/TopRatedMovies.vue b/movie-group-8/src/components/TopRatedMovies.vue new file mode 100644 index 0000000..8b5bfc5 --- /dev/null +++ b/movie-group-8/src/components/TopRatedMovies.vue @@ -0,0 +1,140 @@ +<script setup> +import { ref, onMounted, watch } from 'vue' + +const year = ref(2000) +const genre = ref('') +const genres = ref([]) +const movies = ref([]) + +const TMDB_API_KEY = import.meta.env.VITE_TMDB_API_KEY + +// Fetch genres on component mount +onMounted(async () => { + const genreRes = await fetch(`https://api.themoviedb.org/3/genre/movie/list?api_key=${TMDB_API_KEY}`) + const genreData = await genreRes.json() + genres.value = genreData.genres +}) + +// Watch for changes in year or genre and fetch movies +watch([year, genre], async () => { + const url = `https://api.themoviedb.org/3/discover/movie?api_key=${TMDB_API_KEY}&sort_by=vote_average.desc&vote_count.gte=50&primary_release_year=${year.value}` + + (genre.value ? `&with_genres=${genre.value}` : '') + + const res = await fetch(url) + const data = await res.json() + movies.value = data.results +}, { immediate: true }) +</script> + +<template> + <div class="nyt-header"> + <h1>The Movies We've Loved Since 2000</h1> + <p> + Explore top-rated films using the filters below — by genre and year — and rediscover hidden gems. + </p> + </div> + + <div class="filters"> + <select v-model="genre"> + <option value="">Pick a genre ...</option> + <option v-for="g in genres" :key="g.id" :value="g.id">{{ g.name }}</option> + </select> + + <select v-model="year"> + <option v-for="y in Array.from({length: 25}, (_, i) => 2000 + i)" :key="y" :value="y"> + {{ y }} + </option> + </select> + </div> + + <h2 class="section-title">Our favorite movies from {{ year }}</h2> + + <div class="movie-grid"> + <div v-for="movie in movies" :key="movie.id" class="movie-card"> + <img + v-if="movie.poster_path" + :src="'https://image.tmdb.org/t/p/w342' + movie.poster_path" + alt="poster" + /> + <p class="title">{{ movie.title }}</p> + <p class="rating">â {{ movie.vote_average }}</p> + </div> + </div> + </template> + +<style scoped> +.nyt-header { + text-align: center; + max-width: 700px; + margin: 3rem auto 2rem auto; + padding: 0 1rem; +} +.nyt-header h1 { + font-size: 2.4rem; + font-weight: 700; + line-height: 1.2; +} +.nyt-header p { + font-size: 1.1rem; + margin-top: 0.75rem; + color: #555; +} + +.filters { + display: flex; + justify-content: center; + flex-wrap: wrap; + gap: 1rem; + margin-bottom: 2rem; +} + +.filters select { + padding: 0.5rem 1.2rem; + background-color: #cce8ff; + border: none; + border-radius: 8px; + font-weight: bold; + font-size: 1rem; + color: #003366; + cursor: pointer; + transition: background-color 0.2s ease; +} +.filters select:hover { + background-color: #b2dcff; +} + +.section-title { + text-align: center; + font-size: 1.6rem; + font-weight: bold; + margin-bottom: 1.5rem; +} + +.movie-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + gap: 1.2rem; + padding: 0 1rem; +} + +.movie-card { + text-align: center; +} + +.movie-card img { + width: 100%; + border-radius: 12px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); +} + +.title { + font-weight: 600; + margin-top: 0.5rem; +} + +.rating { + color: #ffa500; + font-size: 0.9rem; +} + +</style> diff --git a/movie-group-8/src/router/index.js b/movie-group-8/src/router/index.js index 21bc9eb..b8f7ba0 100644 --- a/movie-group-8/src/router/index.js +++ b/movie-group-8/src/router/index.js @@ -1,10 +1,15 @@ import { getAuth, onAuthStateChanged } from 'firebase/auth'; import { createRouter, createWebHistory } from 'vue-router'; + + const router = createRouter({ history: createWebHistory(), routes: [ + + { path: '/', component: () => import('../views/Home.vue') }, + {path: '/top-rated', component: () => import('../views/TopRated.vue'), meta: { requiresAuth: true }}, { path: '/register', component: () => import('../views/Register.vue') }, { path: '/login', component: () => import('../views/Login.vue') }, { path: '/recover-account', component: () => import('../views/RecoverAccount.vue') }, diff --git a/movie-group-8/src/views/Films.vue b/movie-group-8/src/views/Films.vue index 072b643..3f3aee4 100644 --- a/movie-group-8/src/views/Films.vue +++ b/movie-group-8/src/views/Films.vue @@ -1 +1,8 @@ -<template>Films</template> \ No newline at end of file +<script setup> +import TopRatedMovies from '@/components/TopRatedMovies.vue' +</script> + +<template> + <TopRatedMovies /> +</template> + diff --git a/movie-group-8/src/views/TopRated.vue b/movie-group-8/src/views/TopRated.vue new file mode 100644 index 0000000..a8e7c29 --- /dev/null +++ b/movie-group-8/src/views/TopRated.vue @@ -0,0 +1,44 @@ +<script setup> +import TopRatedMovies from '@/components/TopRatedMovies.vue' + +</script> + +<template> + <TopRatedMovies /> + <template> + <div class="nyt-header"> + <h1>The Movies We've Loved Since 2000</h1> + <p> + Explore top-rated films using the filters below — by genre and year — and rediscover hidden gems. + </p> + </div> + + <div class="filters"> + <select v-model="genre"> + <option value="">Pick a genre ...</option> + <option v-for="g in genres" :key="g.id" :value="g.id">{{ g.name }}</option> + </select> + + <select v-model="year"> + <option v-for="y in Array.from({length: 25}, (_, i) => 2000 + i)" :key="y" :value="y"> + {{ y }} + </option> + </select> + </div> + + <h2 class="section-title">Our favorite movies from {{ year }}</h2> + + <div class="movie-grid"> + <div v-for="movie in movies" :key="movie.id" class="movie-card"> + <img + v-if="movie.poster_path" + :src="'https://image.tmdb.org/t/p/w342' + movie.poster_path" + alt="poster" + /> + <p class="title">{{ movie.title }}</p> + <p class="rating">â {{ movie.vote_average }}</p> + </div> + </div> +</template> + +</template> diff --git a/movie-group-8/vite.config.js b/movie-group-8/vite.config.js index daa4666..8bbf470 100644 --- a/movie-group-8/vite.config.js +++ b/movie-group-8/vite.config.js @@ -1,10 +1,18 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import tailwindcss from '@tailwindcss/vite' +import path from 'path' + // https://vite.dev/config/ export default defineConfig({ plugins: [vue(), tailwindcss() ], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, + }) -- GitLab