Haz con vue y firebase una vista donde se traigan todos los usuarios y al darle click al nombre sea un router link y te lleve a un chat privado con el usuario, usa currenUser para ocultarlo en este listado, te dejo algunos archivos que tengo como referencia import { chatSubscribeToMessage, chatSaveMessage } from '../services/chat.js' import { subscribeToAuth } from '../services/auth.js' import { formatDate } from '../helpers/date.js' import BaseLabel from '../components/BaseLabel.vue' import BaseButton from '../components/BaseButton.vue' import BaseLoader from '../components/BaseLoader.vue' import PrincipalTitle from '../components/PrincipalTitle.vue' export default { name: 'Chat', components: { BaseButton, BaseLabel, BaseLoader, PrincipalTitle }, data() { return { loadingMessages: true, messages: [], newMessage: { user: '', message: '', }, user: { id: null, email: null, rol: null, }, authUnSubscribe: () => {}, chatUnSubscribe: () => {}, }; }, methods: { sendMessage() { chatSaveMessage({ userId: this.user.id, user: this.user.email, message: this.newMessage.message, }) .then(() => { this.newMessage = ''; }); }, dateToString(date) { return formatDate(date) } }, mounted() { this.loadingMessages = true, this.chatUnSubscribe = chatSubscribeToMessage(messages => { this.messages = messages this.loadingMessages = false }); this.authUnSubscribe = subscribeToAuth(newUser => { this.user = {...newUser} }); }, unmounted() { // Limpiamos la subscripcion a auth this.authUnSubscribe() this.chatUnSubscribe() } } Impresionante chat en tiempo real Usuario: {{ message.user }} Mensaje: {{ message.message }} {{ dateToString(message.created_at) }} <form action="#" @submit.prevent="sendMessage" class="min-w-[320px]"> Usuario {{ this.user.email }} Mensaje Enviar mensaje import BaseButton from '../components/BaseButton.vue'; import BaseLabel from '../components/BaseLabel.vue'; import BaseLoader from '../components/BaseLoader.vue'; import { getUserById } from '../services/user.js'; import { subscribeToAuth } from '../services/auth.js'; import { formatDate } from '../helpers/date.js' import {sendPrivateMessage, subscribeToPrivateChat} from '../services/private-chat.js' import PrincipalTitle from '../components/PrincipalTitle.vue';
export default {
name: 'PrivateChat',
components: { BaseLoader, BaseLabel, BaseButton, PrincipalTitle },
data(){
return {
loadingMessages: true,
messages: [],
loadingProfile: true,
userProfile: {
id:null,
email: null,
},
userAuth:{
id: null,
email: null,
rol: null,
},
newMessage:{
message:'',
},
authUnsubscribe: ()=>{},
messageUnsubscribe: ()=>{},
}
},
methods: {
handleMessage(){
sendPrivateMessage({
sender:this.userAuth.id,
receiver:this.userProfile.id,
message: this.newMessage.message
})
this.newMessage.message = ''
},
dateToString(date) {
return formatDate(date)
}
},
async mounted() {
this.loadingProfile = true
this.userProfile = await getUserById(this.$route.params.id)
this.authUnsubscribe = subscribeToAuth(user => this.userAuth = user)
this.loadingProfile = false
this.loadingMessages = true
this.messageUnsubscribe = await subscribeToPrivateChat(
{user1: this.userProfile.id, user2: this.userAuth.id},
newMessages => this.messages = newMessages
)
this.loadingMessages = false
},
unmounted(){
this.authUnsubscribe()
},
}
Chat privado con: {{ userProfile.email }}
Mensajes
{{ message.message }}
{{ dateToString(message.created_at) || 'enviando...'}}
Enviar un mensaje
<form action="#" @submit.prevent="handleMessage" class="flex">
Mensaje
Enviar
import { createUserWithEmailAndPassword, onAuthStateChanged, signInWithEmailAndPassword, signOut } from "firebase/auth"
import { auth,db } from './firebase.js'
import { createUserProfile } from "./user.js"
import { doc, getDoc } from 'firebase/firestore'
let userData = { id: null, email: null, rol: null, } let observers = []
async function fetchUserProfile(userId) { try { const docRef = doc(db, 'users', userId); const docSnap = await getDoc(docRef);
if (docSnap.exists()) { return docSnap.data(); } else { console.log("No user profile found!"); return null; } } catch (error) { console.error("Error fetching user profile: ", error); return null; }
} // recordamos en localstorage que el usuario esta authenticado. // Esto ahorra tiempo ya que verificamos antes de que firebase nos lo diga. if (localStorage.getItem('userData')) { userData = JSON.parse(localStorage.getItem('userData')) }
onAuthStateChanged(auth, async user => { if (user) { const profile = await fetchUserProfile(user.uid);
if(profile) { userData = { id: user.uid, email: user.email, rol: profile.rol, // ahora accedemos al rol desde la data del perfil }; localStorage.setItem('userData', JSON.stringify(userData)); notifyAll(); } } else { userData = { id: null, email: null, rol: null, }; localStorage.removeItem('userData'); }
})
export async function register({email,password}) { try { const userCredentials = await createUserWithEmailAndPassword(auth, email, password)
createUserProfile(userCredentials.user.uid,{ email, rol: 'client' }) return {...userData} } catch (error) { return { code: error.code, message: error.message } }
}
/** *
export function logout() { return signOut(auth).then(() => { // Aquí limpia cualquier información del usuario que tengas en el estado. userData = { id: null, email: null, rol: null, }; // Remueve el perfil del usuario del localStorage también. localStorage.removeItem('userData'); // Notifica a todos los observadores que el usuario ha cerrado sesión. notifyAll(); console.log("Usuario ha cerrado sesión exitosamente."); }).catch(error => { // Aquí manejas el caso de un error al intentar cerrar la sesión. console.error("Error al cerrar sesión: ", error); }); }
/**
Agrega el observer para ser notificados de los datos
@param {({})=>void} callback
@returns {()=>void} Funcion para cancelar subscripcion */ export function subscribeToAuth(observer) { observers.push(observer) // console.log('Observer agregado', observers); notify(observer)
// cancelamos la subscripcion de los que ya no utilizaremos return () => observers = observers.filter(obs => obs !== observer) }
function notify(callback) { callback({ ...userData, }) }
function notifyAll() { observers.forEach(observer => notify(observer)) } import { addDoc, collection, onSnapshot, orderBy, query, serverTimestamp, getDocs, where, } from "firebase/firestore"; // import { db, query, where, getDocs } from 'firebase/firestore'; import { db } from "./firebase.js"; const refChat = collection(db, "chat");
export function chatSaveMessage(data) { return addDoc(refChat, { ...data, created_at: serverTimestamp(), // Agrega la fecha y hora del mensaje esto lo hacemos para poder ordenar los mensajes }); }
export function chatSubscribeToMessage(callback) { // creamos un query para poder filtrar los datos/traerlos en un orden especifico const q = query(refChat, orderBy("created_at")); return onSnapshot(refChat, (snapshot) => { // datos en tiempo real, trae los datos ordenados por su id por lo que para nosotros parecera que los trae desordenados const messages = snapshot.docs.map((doc) => { return { user: doc.data().user, message: doc.data().message, userId: doc.data().userId, created_at: doc.data().created_at?.toDate(), }; }); callback(messages); }); }
export async function getPrivateChatsForUser(userId, callback) {
const q = query(
collection(db, "private-chats"),
where(users.${userId}
, "==", true)
);
const querySnapshot = await getDocs(q); const userIds = [];
querySnapshot.forEach((doc) => { const users = doc.data().Users; Object.keys(users).forEach((key) => { if (key !== userId && !userIds.includes(key)) { userIds.push(key); } }); });
callback(userIds); } /* Estructura para manejar los chatPrivados: [C] private-chat { [D] idAuto { Users: { IdUser1: true, IdUser2: true, }, [C] messages { [D] idAuth { userId: idUser1, message:..., created_at: Timestamp, }, ... } } } Cada documento sera un chatPrivado que guarda sus integrantes y todos los mensajes */ import { addDoc, collection, serverTimestamp, getDocs, query, where, onSnapshot, orderBy } from 'firebase/firestore' import {db} from './firebase'
// Guardamos los documententos de los chats privados const chatDocsCache = []
/** *
@param {{user1: string, user2: string}} data
@param {()=>{}} callback
@returns {Promise<import("firebase/auth").Unsubscribe } */ export async function subscribeToPrivateChat({user1, user2}, callback){ const chatDoc = await getPrivateChatDocument({user1, user2})
const q = query(
collection(db, private-chats/${chatDoc.id}/messages
),
orderBy('created_at')
)
return onSnapshot(q, snapshot => {
const docs = snapshot.docs.map(doc => {
return {
id: doc.id,
userId: doc.data().userId,
message: doc.data().message,
created_at: doc.data().created_at?.toDate()
}
})
callback(docs)
})
}
/** *
private-chats/${document.id}/messages
)
const response = await addDoc(messagesRef, {
userId: sender,
message,
created_at: serverTimestamp(),
})
return {
id: response.id,
}
}/**
Busca si ya existe un chat entre esos dos usuarios y lo devuelve
en caso contrario crea uno
@param {{user1: string, user2: string}} data
@returns {Promise} */ async function getPrivateChatDocument({user1, user2}){ const doc = getFromDocsCache({user1, user2})// busca el chat en cache if (doc) return doc // si existe lo retorna
const users = { [user1]: true, [user2]: true, } const chatRef = collection(db, 'private-chats') const q = query(chatRef, where('users', '==', users)) const snapshot = await getDocs(q)
let chatDoc = null if(snapshot.empty) { chatDoc = await createPrivateChatDocument({user1: user1, user2: user2}) } else { chatDoc = snapshot.docs[0] } addToDocsCache({user1, user2}, chatDoc) // agrego el chat a cache return chatDoc }
/**
/**
/**
function getDocsCacheKey({user1,user2}) { return user1 > user2 ? user1 + user2: user2 + user1; } import {db} from './firebase' import { doc, getDoc, setDoc } from 'firebase/firestore'
/** *
/users/${id}
))
return {
id,
email: (await snapshot).data().email,
}
}/**
/users/${id}
)
return setDoc(userRef, data)
}Based on the code and details you've provided, it seems like you want to create a view in a Vue.js application that lists all users, allowing you to click on a user's name to navigate to a private chat with that specific user. You also want the current user to be omitted from the list.
I will outline the steps needed to achieve this functionality and provide you with a basic example of what the code could look like. It appears you are using Vue Router for navigation and Firebase Firestore to store and retrieve your chat data.
Steps:
First, let's create a method to fetch all users:
// In your user service or similar file export async function getAllUsers(excludeUserId) { const usersRef = collection(db, 'users'); const q = query(usersRef, where('id', '!=', excludeUserId)); const querySnapshot = await getDocs(q); return querySnapshot.docs.map((doc) => doc.data()); }
Next, you need a Vue component (UserList.vue
) to list all users:
<template> <div> <h1>All Users</h1> <ul> <li v-for="user in users" :key="user.id" v-if="user.id !== currentUser.id"> <router-link :to="`/chat/${user.id}`">{{ user.email }}</router-link> </li> </ul> </div> </template> <script> import { getAllUsers } from 'path-to-your-user-service'; import { subscribeToAuth } from 'path-to-auth-service'; export default { name: 'UserList', data() { return { users: [], currentUser: {} }; }, async created() { this.authUnsubscribe = subscribeToAuth((user) => { this.currentUser = user; this.fetchUsers(user.id); }); }, methods: { async fetchUsers(excludeUserId) { this.users = await getAllUsers(excludeUserId); } }, beforeDestroy() { this.authUnsubscribe(); }, }; </script>
Make sure the route for /chat/:id
is defined in your router/index.js
file to point to your PrivateChat
component:
const routes = [ // ... other routes { path: '/chat/:id', name: 'PrivateChat', component: () => import(/* webpackChunkName: "chat" */ '../views/PrivateChat.vue'), props: true } // ... other routes ]
Lastly, ensure the PrivateChat
component is set up to use the id
route parameter to load the appropriate chat messages:
// Already in your PrivateChat component, within the `mounted` method async mounted() { this.loadingMessages = true; this.loadingProfile = true; // Get the user profile using the id from the route parameters this.userProfile = await getUserById(this.$route.params.id); // Here add the code to not show the current user, assuming userAuth is the current user if(this.userProfile.id === this.userAuth.id) { // Do not display the current user or redirect away } // ... rest of your code }
The provided code snippets are to guide you along the right path, but you'll need to adjust them to fit the exact structure of your application and data.