Skip to content
Snippets Groups Projects
Commit e883407c authored by Miah, Nunu (UG - Comp Sci & Elec Eng)'s avatar Miah, Nunu (UG - Comp Sci & Elec Eng)
Browse files

Fixed register to add user to database and the navigation bar now includes the...

Fixed register to add user to database and the navigation bar now includes the social tab, new users now have a default avatar and the profile view has been changed to have 3 tabs to view the users followers, followings and watchlist
parent 0811c07a
No related branches found
No related tags found
3 merge requests!5Push to Main,!4Resolved Merge Conflicts.,!3Created Social tab to search for users and a public profile view to follow the...
movie-group-8/src/assets/default_avatar.png

38.2 KiB

......@@ -87,6 +87,7 @@ const navigation = ref([
{ name: 'Films', href: '/films', authRequired: true },
{ name: 'Watchlist', href: '/watchlist', authRequired: true },
{ name: 'Top Rated', href: '/top-rated', authRequired: true },
{ name: 'Social', href: '/social', authRequired: true },
]);
......
......@@ -78,6 +78,7 @@ import {
setDoc
} from 'firebase/firestore';
import { useRouter } from 'vue-router';
import defaultAvatar from '@/assets/default_avatar.png'
const email = ref('');
const password = ref('');
......@@ -94,7 +95,7 @@ const createUserIfNotExists = async (user) => {
await setDoc(userRef, {
uid: user.uid,
displayName: user.displayName || 'Anonymous',
photoURL: user.photoURL || '',
photoURL: defaultAvatar
});
}
};
......
......@@ -9,7 +9,7 @@
/>
<h1 class="text-3xl font-bold text-gray-800 dark:text-white">{{ displayName || 'Anonymous' }}</h1>
<p class="text-sm text-gray-600 dark:text-neutral-400">{{ userEmail }}</p>
<p class="mt-2 text-base text-gray-600 dark:text-neutral-400">Movie fan and scene collector.</p>
<p class="mt-2 text-base text-gray-600 dark:text-neutral-400">{{ description }}</p>
<button
@click="showForm = !showForm"
......@@ -19,6 +19,7 @@
</button>
</div>
<!-- Update Form -->
<div v-if="showForm" class="max-w-xl mx-auto mb-12 bg-white dark:bg-neutral-800 p-6 rounded-xl border dark:border-neutral-700">
<form @submit.prevent="handleUpdateProfile" class="grid gap-6">
<div>
......@@ -26,6 +27,11 @@
<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">Description</label>
<textarea v-model="description" rows="3" class="w-full border border-gray-400 rounded-lg p-2 dark:text-white" />
</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" />
......@@ -43,74 +49,114 @@
</form>
</div>
<div>
<h2 class="text-2xl font-semibold text-gray-800 dark:text-white mb-6">Your Watchlist</h2>
<!-- Tabs -->
<div class="flex justify-center gap-4 mb-6">
<button
v-for="tab in ['watchlist', 'followers', 'following']"
:key="tab"
@click="activeTab = tab"
:class="[
'px-4 py-2 rounded-lg text-sm font-medium',
activeTab === tab
? 'bg-blue-600 text-white'
: 'bg-white dark:bg-neutral-700 text-gray-800 dark:text-white border border-gray-300 dark:border-neutral-600'
]"
>
{{ tab.charAt(0).toUpperCase() + tab.slice(1) }}
</button>
</div>
<div v-if="watchlist.length === 0" class="text-gray-500 dark:text-gray-400 text-sm">
Your watchlist is empty.
<!-- Tab Content -->
<div v-if="activeTab === 'watchlist'" class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
<router-link
v-for="movie in watchlist"
:key="movie.id"
:to="`/films/${movie.id}`"
class="block bg-white dark:bg-neutral-800 border dark:border-neutral-700 rounded-lg p-3 hover:opacity-90 transition"
>
<img
v-if="movie.poster_path"
:src="'https://image.tmdb.org/t/p/w342' + movie.poster_path"
alt="Poster"
class="w-full h-60 object-cover rounded mb-3"
/>
<p class="font-semibold text-gray-800 dark:text-white">{{ movie.title }}</p>
<p class="text-sm text-gray-600 dark:text-neutral-400">{{ movie.vote_average }}{{ movie.status }}</p>
</router-link>
</div>
<!-- Followers -->
<div v-else-if="activeTab === 'followers'" class="grid gap-4 max-w-xl mx-auto">
<div
v-for="follower in followers"
:key="follower.uid"
class="flex items-center justify-between p-4 bg-white dark:bg-neutral-800 border dark:border-neutral-700 rounded-lg"
>
<router-link :to="`/user/${follower.uid}`" class="flex items-center gap-4">
<img
:src="follower.photoURL || 'https://via.placeholder.com/150'"
alt="Avatar"
class="w-12 h-12 rounded-full object-cover"
/>
<span class="text-lg font-semibold text-gray-800 dark:text-white">{{ follower.displayName }}</span>
</router-link>
</div>
</div>
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
<div
v-for="movie in watchlist"
:key="movie.id"
class="bg-white dark:bg-neutral-800 border dark:border-neutral-700 rounded-lg p-3 text-left"
>
<!-- Following -->
<div v-else-if="activeTab === 'following'" class="grid gap-4 max-w-xl mx-auto">
<div
v-for="followed in following"
:key="followed.uid"
class="flex items-center justify-between p-4 bg-white dark:bg-neutral-800 border dark:border-neutral-700 rounded-lg"
>
<router-link :to="`/user/${followed.uid}`" class="flex items-center gap-4">
<img
v-if="movie.poster_path"
:src="'https://image.tmdb.org/t/p/w342' + movie.poster_path"
alt="Poster"
class="w-full h-60 object-cover rounded mb-3"
:src="followed.photoURL || 'https://via.placeholder.com/150'"
alt="Avatar"
class="w-12 h-12 rounded-full object-cover"
/>
<p class="font-semibold text-gray-800 dark:text-white">{{ movie.title }}</p>
<p class="text-sm text-gray-600 dark:text-neutral-400">{{ movie.vote_average }}{{ movie.status }}</p>
</div>
<span class="text-lg font-semibold text-gray-800 dark:text-white">{{ followed.displayName }}</span>
</router-link>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import {
collection,
getDocs,
doc,
getDoc,
updateDoc,
getFirestore,
} from 'firebase/firestore'
import {
getAuth,
updateProfile as firebaseUpdateProfile,
updatePassword,
updatePassword
} from 'firebase/auth'
import {
getFirestore,
collection,
getDocs
} from 'firebase/firestore'
import { ref, computed, onMounted } from 'vue'
const auth = getAuth()
const db = getFirestore()
const user = auth.currentUser
const loading = ref(false)
const user = ref(null)
const userEmail = ref('')
const displayName = ref('')
const newPassword = ref('')
const description = ref('')
const userPhotoURL = ref('')
const newPassword = ref('')
const showForm = ref(false)
const activeTab = ref('watchlist')
const loading = ref(false)
const watchlist = ref([])
const followers = ref([])
const following = ref([])
// Load profile info
onMounted(async () => {
if (user) {
userEmail.value = user.email
displayName.value = user.displayName || ''
userPhotoURL.value = user.photoURL || ''
// Fetch watchlist
const snapshot = await getDocs(collection(db, 'users', user.uid, 'watchlist'))
watchlist.value = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }))
}
})
// Password validation
const passwordError = computed(() => {
if (!newPassword.value) return ''
if (newPassword.value.length < 8) return 'Password must be at least 8 characters.'
......@@ -120,30 +166,104 @@ const passwordError = computed(() => {
return ''
})
// Update profile function
const fetchFollowing = async () => {
const followingSnap = await getDocs(collection(db, 'users', user.value.uid, 'following'))
const followedIds = followingSnap.docs.map(doc => doc.id)
const usersList = []
for (const id of followedIds) {
const docSnap = await getDoc(doc(db, 'users', id))
if (docSnap.exists()) {
const data = docSnap.data()
// Only push if the user has a displayName or isn’t hidden
if (!data.hidden && data.displayName) {
usersList.push({ uid: id, ...data })
}
}
}
following.value = usersList
}
const fetchFollowers = async () => {
if (!user.value) return
const allUsersSnap = await getDocs(collection(db, 'users'))
const followersList = []
for (const docSnap of allUsersSnap.docs) {
const otherUid = docSnap.id
if (otherUid === user.value.uid) continue
const followDocRef = doc(db, 'users', otherUid, 'following', user.value.uid)
const followDocSnap = await getDoc(followDocRef)
if (followDocSnap.exists()) {
const otherUserData = docSnap.data()
if (otherUserData.displayName && !otherUserData.hidden) {
followersList.push({ uid: otherUid, ...otherUserData })
}
}
}
followers.value = followersList
}
const handleUpdateProfile = async () => {
if (passwordError.value) return
loading.value = true
try {
if (user) {
await firebaseUpdateProfile(user, {
displayName: displayName.value
})
const authUser = auth.currentUser
if (!authUser) throw new Error('No authenticated user')
if (newPassword.value) {
await updatePassword(user, newPassword.value)
}
// Update the Firebase Auth profile
await firebaseUpdateProfile(authUser, {
displayName: displayName.value
})
alert('Profile updated successfully!')
showForm.value = false
if (newPassword.value) {
await updatePassword(authUser, newPassword.value)
}
// Update your Firestore user document
const userDocRef = doc(db, 'users', authUser.uid)
await updateDoc(userDocRef, {
displayName: displayName.value,
description: description.value
})
alert('Profile updated successfully!')
showForm.value = false
} catch (error) {
alert(error.message)
} finally {
loading.value = false
}
}
onMounted(async () => {
const authUser = auth.currentUser
if (!authUser) return
user.value = authUser
userEmail.value = authUser.email
displayName.value = authUser.displayName || ''
userPhotoURL.value = authUser.photoURL || ''
const userDoc = await getDoc(doc(db, 'users', user.value.uid))
if (userDoc.exists()) {
description.value = userDoc.data().description || ''
}
const snapshot = await getDocs(collection(db, 'users', user.value.uid, 'watchlist'))
watchlist.value = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }))
await fetchFollowing()
await fetchFollowers()
})
</script>
<style scoped>
......
......@@ -62,8 +62,18 @@
<script setup>
import { ref, computed } from 'vue';
import { getAuth, createUserWithEmailAndPassword, signInWithPopup, GoogleAuthProvider } from 'firebase/auth';
import { useRouter } from 'vue-router';
import defaultAvatar from '@/assets/default_avatar.png'
import {
getAuth,
createUserWithEmailAndPassword,
} from 'firebase/auth';
import {
getFirestore,
doc,
setDoc
} from 'firebase/firestore';
const email = ref('');
const password = ref('');
......@@ -93,9 +103,26 @@ const register = async () => {
if (passwordError.value || confirmPasswordError.value) return;
loading.value = true;
try {
const auth = getAuth();
await createUserWithEmailAndPassword(auth, email.value, password.value);
router.push('/films');
const auth = getAuth()
const db = getFirestore()
const userCredential = await createUserWithEmailAndPassword(auth, email.value, password.value)
const user = userCredential.user
// Set default profile info
await updateProfile(user, {
displayName: 'New User',
photoURL: defaultAvatar
})
// Create Firestore document for this user
await setDoc(doc(db, 'users', user.uid), {
displayName: 'New User',
photoURL: defaultAvatar,
description: '',
hidden: false
})
router.push('/films')
} catch (error) {
alert(error.message);
} finally {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment