diff --git a/movie-group-8/src/views/Profile.vue b/movie-group-8/src/views/Profile.vue index 065de78bfe03ba517b7fdb499bcce3d8981ec898..94358a61e2fbb94778cbbba0fb77cd7cdc36f920 100644 --- a/movie-group-8/src/views/Profile.vue +++ b/movie-group-8/src/views/Profile.vue @@ -52,7 +52,7 @@ <!-- Tabs --> <div class="flex justify-center gap-4 mb-6"> <button - v-for="tab in ['watchlist', 'followers', 'following']" + v-for="tab in ['watchlist', 'followers', 'following', 'reviews']" :key="tab" @click="activeTab = tab" :class="[ @@ -120,6 +120,21 @@ </router-link> </div> </div> + + <!-- Reviews --> + <div v-else-if="activeTab === 'reviews'" class="max-w-2xl mx-auto space-y-6"> + <div v-if="reviewsLoading" class="text-gray-600 dark:text-gray-400">Loading reviews…</div> + <div v-else-if="!reviews.length" class="text-gray-600 dark:text-gray-400">No reviews yet.</div> + <div v-else class="space-y-4"> + <div v-for="r in reviews" :key="r.id" class="bg-white dark:bg-neutral-800 p-4 rounded-lg border dark:border-neutral-700 text-left"> + <router-link :to="`/films/${r.movieId}`" class="font-semibold text-lg text-blue-600 dark:text-blue-400 hover:underline"> + {{ r.movieTitle }} + </router-link> + <div class="mt-1 text-sm text-gray-800 dark:text-gray-200">â {{ r.rating }}</div> + <p class="mt-2 text-gray-700 dark:text-neutral-300">{{ r.text }}</p> + </div> + </div> + </div> </div> </template> @@ -140,6 +155,9 @@ import { } from 'firebase/auth' import { ref, computed, onMounted } from 'vue' + +const TMDB_API_KEY = import.meta.env.VITE_TMDB_API_KEY + const auth = getAuth() const db = getFirestore() @@ -156,6 +174,8 @@ const loading = ref(false) const watchlist = ref([]) const followers = ref([]) const following = ref([]) +const reviews = ref([]) +const reviewsLoading = ref(true) const passwordError = computed(() => { if (!newPassword.value) return '' @@ -210,6 +230,33 @@ const fetchFollowers = async () => { followers.value = followersList } +const fetchReviews = async () => { + const uid = user.value.uid + const snap = await getDocs(collection(db, 'users', uid, 'reviews')) + const revDocs = snap.docs.map(d => ({ id: d.id, ...d.data() })) + + // Fetch all movie titles in parallel + const withTitles = await Promise.all( + revDocs.map(async r => { + // Call TMDB for each movieId + const res = await fetch( + `https://api.themoviedb.org/3/movie/${r.movieId}?api_key=${TMDB_API_KEY}` + ) + const movieData = await res.json() + return { + id: r.id, + movieId: r.movieId, + movieTitle: movieData.title, + rating: r.rating, + text: r.text, + } + }) + ) + + reviews.value = withTitles + reviewsLoading.value = false +} + const handleUpdateProfile = async () => { if (passwordError.value) return @@ -263,6 +310,7 @@ onMounted(async () => { await fetchFollowing() await fetchFollowers() + await fetchReviews() }) </script> diff --git a/movie-group-8/src/views/UserProfile.vue b/movie-group-8/src/views/UserProfile.vue index 74c0cfc15940553b93dad02694bb0a5e0f82de7d..e3b650ebab1cd98051851e8b453fffa22cf2fe62 100644 --- a/movie-group-8/src/views/UserProfile.vue +++ b/movie-group-8/src/views/UserProfile.vue @@ -29,7 +29,7 @@ No public watchlist found. </div> - <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"> + <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 mb-12"> <router-link v-for="movie in watchlist" :key="movie.id" @@ -47,13 +47,39 @@ <p class="text-xs text-gray-400 dark:text-neutral-500">{{ movie.status }}</p> </router-link> </div> + + <h2 class="text-xl font-semibold text-gray-800 dark:text-white mb-4 text-center"> + Reviews + </h2> + <div v-if="reviewsLoading" class="text-center text-gray-500 dark:text-gray-400"> + Loading reviews… + </div> + <div v-else-if="!reviews.length" class="text-gray-400"> + No reviews yet. + </div> + <div class="space-y-6 max-w-3xl mx-auto"> + <div + v-for="r in reviews" + :key="r.id" + class="bg-white dark:bg-neutral-800 p-4 rounded-lg border dark:border-neutral-700 text-left" + > + <router-link + :to="`/films/${r.movieId}`" + class="block text-lg font-semibold text-blue-600 dark:text-blue-400 hover:underline" + > + {{ r.movieTitle }} + </router-link> + <div class="mt-1 text-sm text-gray-800 dark:text-gray-200">â {{ r.rating }}</div> + <p class="mt-2 text-gray-700 dark:text-neutral-300">{{ r.text }}</p> + </div> + </div> </div> </template> <script setup> import { ref, onMounted } from 'vue' import { useRoute } from 'vue-router' -import { getAuth } from 'firebase/auth' +import { getAuth, onAuthStateChanged } from 'firebase/auth' import { getFirestore, doc, @@ -69,17 +95,34 @@ const db = getFirestore() const auth = getAuth() const profileUserId = route.params.id -const currentUser = auth.currentUser const user = ref({}) const watchlist = ref([]) const isFollowing = ref(false) -const isOwnProfile = currentUser?.uid === profileUserId +const isOwnProfile = ref(false) +const TMDB_API_KEY = import.meta.env.VITE_TMDB_API_KEY +const reviews = ref([]) +const reviewsLoading = ref(true) onMounted(async () => { - await fetchUserProfile() - await fetchWatchlist() - if (!isOwnProfile) await checkIfFollowing() + onAuthStateChanged(auth, async (u) => { + if (!u) { + // not logged in → just show public data + await fetchUserProfile() + await fetchWatchlist() + await fetchReviews() + return + } + + // now we have a valid user + isOwnProfile.value = (u.uid === profileUserId) + await fetchUserProfile() + await fetchWatchlist() + if (!isOwnProfile.value) { + await checkIfFollowing(u.uid) + } + await fetchReviews() + }) }) const fetchUserProfile = async () => { @@ -92,19 +135,49 @@ const fetchUserProfile = async () => { } } +async function fetchReviews() { + const snap = await getDocs(collection(db, 'users', profileUserId, 'reviews')) + const revs = snap.docs.map(d => ({ + id: d.id, + ...d.data(), + movieId: d.data().movieId, + text: d.data().text, + rating: d.data().rating, + })) + + // lookup titles in parallel + const withTitles = await Promise.all( + revs.map(async r => { + const res = await fetch( + `https://api.themoviedb.org/3/movie/${r.movieId}?api_key=${TMDB_API_KEY}` + ) + const md = await res.json() + return { + ...r, + movieTitle: md.title + } + }) + ) + + reviews.value = withTitles + reviewsLoading.value = false +} + const fetchWatchlist = async () => { const snapshot = await getDocs(collection(db, 'users', profileUserId, 'watchlist')) watchlist.value = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })) } -const checkIfFollowing = async () => { - const followRef = doc(db, 'users', currentUser.uid, 'following', profileUserId) +async function checkIfFollowing(currentUid) { + const followRef = doc(db, 'users', currentUid, 'following', profileUserId) const followSnap = await getDoc(followRef) isFollowing.value = followSnap.exists() } const toggleFollow = async () => { - const followRef = doc(db, 'users', currentUser.uid, 'following', profileUserId) + const u = auth.currentUser + if (!u) return + const followRef = doc(db, 'users', u.uid, 'following', profileUserId) if (isFollowing.value) { await deleteDoc(followRef)