diff --git a/movie-group-8/.env b/movie-group-8/.env index 1132aa3f35b4d71e10428dc9a3b7f99110b59a17..4ec34fde83f0116c4ff586e6f695eb0a151ad951 100644 --- a/movie-group-8/.env +++ b/movie-group-8/.env @@ -1,2 +1,3 @@ VITE_TMDB_API_KEY=42259df77843511296d8096fa29e08a8 + VITE_TMDB_BASE=https://api.themoviedb.org/3 diff --git a/movie-group-8/src/components/TopRatedMovies.vue b/movie-group-8/src/components/TopRatedMovies.vue index 8b5bfc579781e6ae9467420581cd36848010a234..89845c5502db60f794bd399f34cd1fa56da71a8b 100644 --- a/movie-group-8/src/components/TopRatedMovies.vue +++ b/movie-group-8/src/components/TopRatedMovies.vue @@ -1,5 +1,6 @@ <script setup> import { ref, onMounted, watch } from 'vue' +import addToWatchlist from '@/views/Watchlist.vue' const year = ref(2000) const genre = ref('') @@ -58,6 +59,7 @@ watch([year, genre], async () => { /> <p class="title">{{ movie.title }}</p> <p class="rating">â {{ movie.vote_average }}</p> + <button @click="addToWatchlist(movie)">Add to Watchlist</button> </div> </div> </template> diff --git a/movie-group-8/src/components/Watchlist.vue b/movie-group-8/src/components/Watchlist.vue new file mode 100644 index 0000000000000000000000000000000000000000..f4f4aadaed9e7001aa2a16e99f1cc00984980767 --- /dev/null +++ b/movie-group-8/src/components/Watchlist.vue @@ -0,0 +1,74 @@ +<template> + <div class="watchlist"> + <h1>Your Watchlist</h1> + + <div class="filters"> + <select v-model="filter"> + <option value="">All</option> + <option value="planned">Plan To Watch</option> + <option value="watched">Watched</option> + </select> + </div> + + <div class="movie-grid"> + <div + v-for="movie in filteredWatchlist" + :key="movie.id" + class="movie-card" + > + <img :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> + <p class="status">📌 {{ movie.status }}</p> + </div> + </div> + </div> +</template> + +<script setup> + import { ref, computed, onMounted } from 'vue' + import { getFirestore, collection, getDocs } from 'firebase/firestore' + import { getAuth } from 'firebase/auth' + + const db = getFirestore() + const auth = getAuth() + + const watchlist = ref([]) + const filter = ref('') + + const fetchWatchlist = async () => { + const user = auth.currentUser + if (!user) return + + const snapshot = await getDocs(collection(db, 'users', user.uid, 'watchlist')) + watchlist.value = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })) + } + + const filteredWatchlist = computed(() => { + if (!filter.value) return watchlist.value + return watchlist.value.filter(movie => movie.status === filter.value) + }) + + onMounted(() => { + fetchWatchlist() + }) +</script> + +<style scoped> + /* Reuse your styles or tweak as needed */ + .watchlist { + padding: 2rem; + text-align: center; + } + + .filters { + margin-bottom: 1rem; + } + + .movie-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + gap: 1.2rem; + } +</style> + \ No newline at end of file diff --git a/movie-group-8/src/views/Films.vue b/movie-group-8/src/views/Films.vue index 558dacc95d610d315bf3547b4f0b81ee773b1489..31474cbb988dd844fe1c2a8548fef94c70db2bfa 100644 --- a/movie-group-8/src/views/Films.vue +++ b/movie-group-8/src/views/Films.vue @@ -103,4 +103,4 @@ onMounted(fetchPopular) margin: 1rem 0; font-weight: bold; } -</style> \ No newline at end of file +</style> diff --git a/movie-group-8/src/views/Profile.vue b/movie-group-8/src/views/Profile.vue index 3314b81e266a166a519fa0ee6c5461b23a8be0c0..7e01bc735dbae92af82aaa50e9e4bf31980eaab9 100644 --- a/movie-group-8/src/views/Profile.vue +++ b/movie-group-8/src/views/Profile.vue @@ -1 +1,103 @@ -<template>Profile</template> \ No newline at end of file +<template> + <div class="flex items-center justify-center min-h-screen bg-gray-100 dark:bg-neutral-900"> + <div class="mt-7 bg-white border border-gray-200 rounded-xl shadow-2xs dark:bg-neutral-900 dark:border-neutral-700 min-w-96"> + <div class="p-4 sm:p-7"> + <div class="text-center"> + <h1 class="block text-2xl font-bold text-gray-800 dark:text-white">Profile</h1> + <p class="mt-2 text-sm text-gray-600 dark:text-neutral-400"> + Manage your account information + </p> + </div> + + <div class="mt-5"> + <form @submit.prevent="updateProfile"> + <div class="grid gap-y-4"> + <div> + <label class="block text-sm mb-2 dark:text-white">Email</label> + <input type="email" :value="userEmail" disabled class="w-full border border-gray-400 rounded-lg p-2 bg-gray-100 dark:bg-neutral-800 dark:text-white" /> + </div> + + <div> + <label class="block text-sm mb-2 dark:text-white">Display Name</label> + <input type="text" v-model="displayName" class="w-full border border-gray-400 rounded-lg p-2 dark:text-white" required /> + </div> + + <div> + <label class="block text-sm mb-2 dark:text-white">New Password</label> + <input type="password" v-model="newPassword" class="w-full border border-gray-400 rounded-lg p-2 dark:text-white" /> + <p v-if="newPassword && passwordError" class="text-red-500 text-sm mt-1">{{ passwordError }}</p> + </div> + + <button + type="submit" + :disabled="loading || !!passwordError" + class="w-full py-3 bg-blue-600 text-white rounded-lg flex justify-center items-center gap-2 hover:bg-blue-700 transition duration-300 disabled:opacity-50 disabled:pointer-events-none" + > + <span v-if="!loading">Update Profile</span> + <span v-else>Saving...</span> + </button> + </div> + </form> + </div> + </div> + </div> + </div> +</template> + +<script setup> + import { ref, computed, onMounted } from 'vue'; + import { getAuth, updateProfile, updatePassword } from 'firebase/auth'; + + const auth = getAuth(); + const user = auth.currentUser; + + const loading = ref(false); + const userEmail = ref(''); + const displayName = ref(''); + const newPassword = ref(''); + + // Load current profile info + onMounted(() => { + if (user) { + userEmail.value = user.email; + displayName.value = user.displayName || ''; + } + }); + + // Password validation + const passwordError = computed(() => { + if (!newPassword.value) return ''; + if (newPassword.value.length < 8) return "Password must be at least 8 characters."; + if (!/[A-Z]/.test(newPassword.value)) return "Must include at least 1 uppercase letter."; + if (!/[0-9]/.test(newPassword.value)) return "Must include at least 1 number."; + if (!/[@$!%*?&]/.test(newPassword.value)) return "Must include at least 1 special character (@$!%*?&)."; + return ''; + }); + + // Update function + const updateProfile = async () => { + if (passwordError.value) return; + + loading.value = true; + try { + if (user) { + // Update display name + await updateProfile(user, { + displayName: displayName.value, + }); + + // Update password if provided + if (newPassword.value) { + await updatePassword(user, newPassword.value); + } + + alert('Profile updated successfully!'); + } + } catch (error) { + alert(error.message); + } finally { + loading.value = false; + } + }; +</script> + \ No newline at end of file diff --git a/movie-group-8/src/views/Watchlist.vue b/movie-group-8/src/views/Watchlist.vue index 881fa1732ebc44f101dee618914366c413913c24..58cf86606685c77a69557491d6f210d3ba2ca52d 100644 --- a/movie-group-8/src/views/Watchlist.vue +++ b/movie-group-8/src/views/Watchlist.vue @@ -1 +1,26 @@ -<template>Watchlist</template> \ No newline at end of file +<template> + <Watchlist /> +</template> + +<script setup> +import Watchlist from '@/components/Watchlist.vue' + +import { getFirestore, doc, setDoc } from 'firebase/firestore' +import { getAuth } from 'firebase/auth' + +const addToWatchlist = async (movie) => { + const user = getAuth().currentUser + if (!user) return alert('You need to log in.') + + const db = getFirestore() + const movieRef = doc(db, 'users', user.uid, 'watchlist', String(movie.id)) + await setDoc(movieRef, { + title: movie.title, + poster_path: movie.poster_path, + vote_average: movie.vote_average, + status: 'planned' + }) + + alert('Movie added to watchlist!') +} +</script> \ No newline at end of file diff --git a/movie-group-8/vite.config.js b/movie-group-8/vite.config.js index 2748a5d1734b00cc595b52b7e3553cd4b3e9e638..8bbf4703424d6582b10489e90bdad6599bfa8345 100644 --- a/movie-group-8/vite.config.js +++ b/movie-group-8/vite.config.js @@ -15,4 +15,4 @@ export default defineConfig({ }, }, -}) \ No newline at end of file +})