From 8bc6c0b32cbdd68f3901b769cdab8def33f0725a Mon Sep 17 00:00:00 2001
From: sc02613 <sc02613@surrey.ac.uk>
Date: Thu, 22 May 2025 00:47:01 +0100
Subject: [PATCH] The paths and views for review page added

---
 movie-group-8/src/router/index.js       |   9 +
 movie-group-8/src/views/FilmDetails.vue | 339 ++++++++++++------------
 movie-group-8/src/views/ReviewPage.vue  | 230 ++++++++++++++++
 3 files changed, 415 insertions(+), 163 deletions(-)
 create mode 100644 movie-group-8/src/views/ReviewPage.vue

diff --git a/movie-group-8/src/router/index.js b/movie-group-8/src/router/index.js
index 8cad4e4..f4763b4 100644
--- a/movie-group-8/src/router/index.js
+++ b/movie-group-8/src/router/index.js
@@ -2,6 +2,7 @@ import { getAuth, onAuthStateChanged } from 'firebase/auth';
 import { createRouter, createWebHistory } from 'vue-router';
 import Films from '@/views/Films.vue'
 import FilmDetails from '@/views/FilmDetails.vue'
+import ReviewPage from '@/views/ReviewPage.vue'
 
 const router = createRouter({
     history: createWebHistory(),
@@ -25,6 +26,13 @@ const router = createRouter({
             component: FilmDetails,
         },
 
+        {
+            path: '/films/:id/review',
+            name: 'ReviewFilm',
+            component: ReviewPage,
+            props: true,
+        },
+
         { 
             path: '/watchlist', 
             component: () => import('../views/WatchlistView.vue'), 
@@ -40,6 +48,7 @@ const router = createRouter({
             component: () => import('../views/Settings.vue'), 
             meta: { requiresAuth: true },
         },
+
     ],
 });
 
diff --git a/movie-group-8/src/views/FilmDetails.vue b/movie-group-8/src/views/FilmDetails.vue
index 0e8eda8..8194f5c 100644
--- a/movie-group-8/src/views/FilmDetails.vue
+++ b/movie-group-8/src/views/FilmDetails.vue
@@ -1,170 +1,183 @@
 <template>
-    <div class="film-details">
-      <button class="back-button" @click="$router.back()">← Back</button>
-  
-      <div v-if="loading" class="loading">Loading movie...</div>
-      <div v-else-if="error" class="error">Error: {{ error }}</div>
-  
-      <div v-else class="details-container">
-        <div class="poster-container">
-          <img
-            v-if="movie.poster_path"
-            :src="`https://image.tmdb.org/t/p/w500${movie.poster_path}`"
-            :alt="`${movie.title} poster`"
-            class="poster"
-          />
-        </div>
-  
-        <div class="info-container">
-          <h1 class="title">{{ movie.title }}</h1>
-          <p class="release-date">{{ formatDate(movie.release_date) }}</p>
-          <p class="overview">{{ movie.overview }}</p>
-  
-          <div v-if="trailerUrl" class="trailer">
-            <h2>Trailer</h2>
-            <iframe
-              :src="trailerUrl"
-              frameborder="0"
-              allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
-              allowfullscreen
-            ></iframe>
-          </div>
-          <button @click="addToWatchlist(movie)">Add to Watchlist</button>
+  <div class="film-details">
+    <button class="back-button" @click="$router.back()">← Back</button>
+
+    <div v-if="loading" class="loading">Loading movie...</div>
+    <div v-else-if="error" class="error">Error: {{ error }}</div>
+
+    <div v-else class="details-container">
+      <div class="poster-container">
+        <img
+          v-if="movie.poster_path"
+          :src="`https://image.tmdb.org/t/p/w500${movie.poster_path}`"
+          :alt="`${movie.title} poster`"
+          class="poster"
+        />
+      </div>
+
+      <div class="info-container">
+        <h1 class="title">{{ movie.title }}</h1>
+        <p class="release-date">{{ formatDate(movie.release_date) }}</p>
+        <p class="overview">{{ movie.overview }}</p>
+
+        <div v-if="trailerUrl" class="trailer">
+          <h2>Trailer</h2>
+          <iframe
+            :src="trailerUrl"
+            frameborder="0"
+            allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
+            allowfullscreen
+          ></iframe>
         </div>
+
+        <button
+          @click="addToWatchlist(movie)"
+          class="mt-4 px-4 py-2 bg-green-600 rounded text-white hover:bg-green-700"
+        >
+          Add to Watchlist
+        </button>
+
+        <RouterLink
+          :to="{ name: 'ReviewFilm', params: { id: movie.id } }"
+          class="mt-2 inline-block px-4 py-2 bg-blue-600 rounded text-white hover:bg-blue-700 text-center"
+        >
+          Review this Film
+        </RouterLink>
       </div>
     </div>
-  </template>
-  
-  <script setup>
-  import { ref, onMounted } from 'vue'
-  import { useRoute } from 'vue-router'
-  import { addToWatchlist } from '@/composables/useWatchlist.js'
-  
-  const route = useRoute()
-  const movie = ref(null)
-  const trailerUrl = ref('')
-  const loading = ref(true)
-  const error = ref('')
-  
-  function formatDate(dateString) {
-    if (!dateString) return 'Unknown'
-    return new Date(dateString).toLocaleDateString(undefined, {
-      year: 'numeric',
-      month: 'long',
-      day: 'numeric'
-    })
-  }
-  
-  async function fetchMovieDetails(id) {
-    try {
-      const res = await fetch(
-        `${import.meta.env.VITE_TMDB_BASE}/movie/${id}?api_key=${import.meta.env.VITE_TMDB_API_KEY}`
-      )
-      if (!res.ok) throw new Error('Failed to fetch movie details')
-      movie.value = await res.json()
-    } catch (err) {
-      error.value = err.message
-    }
-  }
-  
-  async function fetchMovieVideos(id) {
-    try {
-      const res = await fetch(
-        `${import.meta.env.VITE_TMDB_BASE}/movie/${id}/videos?api_key=${import.meta.env.VITE_TMDB_API_KEY}`
-      )
-      if (!res.ok) throw new Error('Failed to fetch movie videos')
-      const data = await res.json()
-      const trailer = data.results.find(v => v.type === 'Trailer' && v.site === 'YouTube')
-      if (trailer) {
-        trailerUrl.value = `https://www.youtube.com/embed/${trailer.key}`
-      }
-    } catch {
-      // ignore trailer errors
-    }
-  }
-  
-  onMounted(async () => {
-    const id = route.params.id
-    await fetchMovieDetails(id)
-    await fetchMovieVideos(id)
-    loading.value = false
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useRoute } from 'vue-router'
+import { addToWatchlist } from '@/composables/useWatchlist.js'
+
+const route = useRoute()
+const movie = ref(null)
+const trailerUrl = ref('')
+const loading = ref(true)
+const error = ref('')
+
+function formatDate(dateString) {
+  if (!dateString) return 'Unknown'
+  return new Date(dateString).toLocaleDateString(undefined, {
+    year: 'numeric',
+    month: 'long',
+    day: 'numeric'
   })
-  </script>
-  
-  <style scoped>
-  .film-details {
-    background-color: #121212;
-    color: #ffffff;
-    min-height: 100vh;
-    padding: 2rem;
-    box-sizing: border-box;
-  }
-  
-  .back-button {
-    color: #ffffff;
-    background: transparent;
-    margin-bottom: 1rem;
-    font-size: 1rem;
-  }
-  
-  .loading,
-  .error {
-    color: #ffffff;
-    text-align: center;
-    margin: 2rem 0;
+}
+
+async function fetchMovieDetails(id) {
+  try {
+    const res = await fetch(
+      `${import.meta.env.VITE_TMDB_BASE}/movie/${id}?api_key=${import.meta.env.VITE_TMDB_API_KEY}`
+    )
+    if (!res.ok) throw new Error('Failed to fetch movie details')
+    movie.value = await res.json()
+  } catch (err) {
+    error.value = err.message
   }
-  
-  .details-container {
-    display: flex;
-    flex-wrap: wrap;
-    gap: 2rem;
-    background-color: #1e1e1e;
-    padding: 2rem;
-    border-radius: 8px;
-    max-width: 900px;
-    margin: 2rem auto 0;
-  }
-  
-  .poster-container {
-    flex: 0 0 300px;
-  }
-  
-  .poster {
-    width: 100%;
-    border-radius: 4px;
-  }
-  
-  .info-container {
-    flex: 1;
-    display: flex;
-    flex-direction: column;
-  }
-  
-  .title {
-    font-size: 2rem;
-    margin: 0 0 0.5rem;
-  }
-  
-  .release-date {
-    color: #bbbbbb;
-    margin-bottom: 1rem;
-  }
-  
-  .overview {
-    color: #dddddd;
-    line-height: 1.6;
-    margin-bottom: 1.5rem;
-  }
-  
-  .trailer h2 {
-    margin-bottom: 0.5rem;
-    color: #ffffff;
-  }
-  
-  .trailer iframe {
-    width: 100%;
-    height: 300px;
-    border-radius: 8px;
-    border: none;
+}
+
+async function fetchMovieVideos(id) {
+  try {
+    const res = await fetch(
+      `${import.meta.env.VITE_TMDB_BASE}/movie/${id}/videos?api_key=${import.meta.env.VITE_TMDB_API_KEY}`
+    )
+    if (!res.ok) throw new Error('Failed to fetch movie videos')
+    const data = await res.json()
+    const trailer = data.results.find(v => v.type === 'Trailer' && v.site === 'YouTube')
+    if (trailer) {
+      trailerUrl.value = `https://www.youtube.com/embed/${trailer.key}`
+    }
+  } catch {
+    // ignore trailer errors
   }
-  </style>
\ No newline at end of file
+}
+
+onMounted(async () => {
+  const id = route.params.id
+  await fetchMovieDetails(id)
+  await fetchMovieVideos(id)
+  loading.value = false
+})
+</script>
+
+<style scoped>
+.film-details {
+  background-color: #121212;
+  color: #ffffff;
+  min-height: 100vh;
+  padding: 2rem;
+  box-sizing: border-box;
+}
+
+.back-button {
+  color: #ffffff;
+  background: transparent;
+  margin-bottom: 1rem;
+  font-size: 1rem;
+}
+
+.loading,
+.error {
+  color: #ffffff;
+  text-align: center;
+  margin: 2rem 0;
+}
+
+.details-container {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 2rem;
+  background-color: #1e1e1e;
+  padding: 2rem;
+  border-radius: 8px;
+  max-width: 900px;
+  margin: 2rem auto 0;
+}
+
+.poster-container {
+  flex: 0 0 300px;
+}
+
+.poster {
+  width: 100%;
+  border-radius: 4px;
+}
+
+.info-container {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+}
+
+.title {
+  font-size: 2rem;
+  margin: 0 0 0.5rem;
+}
+
+.release-date {
+  color: #bbbbbb;
+  margin-bottom: 1rem;
+}
+
+.overview {
+  color: #dddddd;
+  line-height: 1.6;
+  margin-bottom: 1.5rem;
+}
+
+.trailer h2 {
+  margin-bottom: 0.5rem;
+  color: #ffffff;
+}
+
+.trailer iframe {
+  width: 100%;
+  height: 300px;
+  border-radius: 8px;
+  border: none;
+}
+</style>
diff --git a/movie-group-8/src/views/ReviewPage.vue b/movie-group-8/src/views/ReviewPage.vue
new file mode 100644
index 0000000..3f130f9
--- /dev/null
+++ b/movie-group-8/src/views/ReviewPage.vue
@@ -0,0 +1,230 @@
+<template>
+    <div class="review-page">
+      <!-- Back -->
+      <button class="back-button" @click="$router.back()">← Back</button>
+  
+      <!-- Loading / Error states -->
+      <div v-if="loading" class="loading">Loading movie...</div>
+      <div v-else-if="error" class="error">Error: {{ error }}</div>
+  
+      <!-- Main content -->
+      <div v-else class="content">
+        <!-- Poster & Title -->
+        <div class="header">
+          <img
+            v-if="movie.poster_path"
+            :src="`https://image.tmdb.org/t/p/w300${movie.poster_path}`"
+            :alt="movie.title + ' poster'"
+            class="poster"
+          />
+          <h1 class="title">{{ movie.title }}</h1>
+        </div>
+  
+        <!-- Star Rating -->
+        <div class="rating">
+          <span
+            v-for="star in 5"
+            :key="star"
+            class="star"
+            :class="{ filled: star <= userRating }"
+            @click="setRating(star)"
+          >
+            ★
+          </span>
+          <span class="rating-value">{{ userRating }} / 5</span>
+        </div>
+  
+        <!-- Favourite Toggle -->
+        <button
+          class="favourite-btn"
+          :class="{ fav: isFavourite }"
+          @click="toggleFavourite"
+        >
+          {{ isFavourite ? '★ Favourite' : '☆ Add to Favourites' }}
+        </button>
+  
+        <!-- Review Text -->
+        <textarea
+          v-model="reviewText"
+          placeholder="Write your review…"
+          class="review-text"
+          rows="6"
+        ></textarea>
+  
+        <!-- Submit -->
+        <button class="submit-btn" @click="submitReview">
+          Submit Review
+        </button>
+      </div>
+    </div>
+  </template>
+  
+  <script setup>
+  import { ref, onMounted } from 'vue'
+  import { useRoute, useRouter } from 'vue-router'
+  
+  const route = useRoute()
+  const router = useRouter()
+  
+  // State
+  const movie        = ref(null)
+  const loading      = ref(true)
+  const error        = ref('')
+  const userRating   = ref(0)
+  const isFavourite  = ref(false)
+  const reviewText   = ref('')
+  
+  // Fetch movie details
+  async function fetchMovie() {
+    try {
+      const id  = route.params.id
+      const res = await fetch(
+        `${import.meta.env.VITE_TMDB_BASE}/movie/${id}?api_key=${import.meta.env.VITE_TMDB_API_KEY}`
+      )
+      if (!res.ok) throw new Error('Failed to load movie')
+      movie.value = await res.json()
+    } catch (err) {
+      error.value = err.message
+    } finally {
+      loading.value = false
+    }
+  }
+  
+  // Rating handlers
+  function setRating(star) {
+    userRating.value = star
+  }
+  
+  // Favourite toggle
+  function toggleFavourite() {
+    isFavourite.value = !isFavourite.value
+  }
+  
+  // Submit handler (stub — wire up to Firestore or your backend)
+  function submitReview() {
+    console.log({
+      movieId: route.params.id,
+      rating: userRating.value,
+      favourite: isFavourite.value,
+      review: reviewText.value,
+    })
+    // e.g. use Firestore:
+    // const db = getFirestore(); 
+    // setDoc(doc(db, 'reviews', `${user.uid}_${movieId}`), { ... })
+    router.back()
+  }
+  
+  onMounted(fetchMovie)
+  </script>
+  
+  <style scoped>
+  .review-page {
+    background: #121212;
+    color: #fff;
+    min-height: 100vh;
+    padding: 2rem;
+    box-sizing: border-box;
+  }
+  
+  .back-button {
+    background: transparent;
+    border: none;
+    color: #fff;
+    font-size: 1rem;
+  }
+  
+  .loading,
+  .error {
+    text-align: center;
+    margin: 2rem 0;
+  }
+  
+  .content {
+    max-width: 600px;
+    margin: 2rem auto;
+    background: #1e1e1e;
+    padding: 2rem;
+    border-radius: 8px;
+  }
+  
+  .header {
+    display: flex;
+    align-items: center;
+    gap: 1rem;
+    margin-bottom: 1.5rem;
+  }
+  
+  .poster {
+    width: 100px;
+    border-radius: 4px;
+  }
+  
+  .title {
+    font-size: 1.75rem;
+    margin: 0;
+  }
+  
+  .rating {
+    display: flex;
+    align-items: center;
+    margin-bottom: 1rem;
+  }
+  
+  .star {
+    font-size: 2rem;
+    cursor: pointer;
+    transition: transform 0.1s;
+    margin-right: 0.25rem;
+    color: #555;
+  }
+  .star.filled {
+    color: #f5c518;
+  }
+  .star:hover {
+    transform: scale(1.2);
+  }
+  
+  .rating-value {
+    margin-left: 0.5rem;
+    color: #bbb;
+  }
+  
+  .favourite-btn {
+    background: #333;
+    border: none;
+    color: #bbb;
+    padding: 0.5rem 1rem;
+    border-radius: 4px;
+    cursor: pointer;
+    margin-bottom: 1rem;
+  }
+  .favourite-btn.fav {
+    background: #d32f2f;
+    color: #fff;
+  }
+  
+  .review-text {
+    width: 100%;
+    padding: 0.75rem;
+    border: none;
+    border-radius: 4px;
+    background: #2a2a2a;
+    color: #fff;
+    margin-bottom: 1rem;
+    resize: vertical;
+  }
+  
+  .submit-btn {
+    background: #1976d2;
+    border: none;
+    color: #fff;
+    padding: 0.75rem 1.5rem;
+    border-radius: 4px;
+    cursor: pointer;
+    width: 100%;
+  }
+  .submit-btn:hover {
+    background: #1565c0;
+  }
+  </style>
+  
\ No newline at end of file
-- 
GitLab