diff --git a/src/server.js b/src/server.js index b3376c9..587e7f0 100644 --- a/src/server.js +++ b/src/server.js @@ -196,236 +196,14 @@ import { ObjectId } from 'mongodb/lib/bson.js'; const vaSocketRespectToClient = new Map(); -io.use(async (socket, next) => { - try { - const token = socket.handshake.auth?.token; - const projectId = socket.handshake.auth?.projectId; - const userType = socket.handshake.auth?.userType; - - console.log("🔐 Received token:", token); - - if (!token) { - return next(new Error("Not Authorized: Missing Token")); - } - - const decoded = jwt.verify(token, process.env.JWT_SECRET_ACCESS_TOKEN); - - if (!decoded?.email) { - return next(new Error("Invalid token payload")); - } - - /* ---------------------- 1⃣ Check if VA ---------------------- */ - let vaUser = null; - let clientData = null; - - if (userType === "va") { - vaUser = await VAModel.findOne({ "userDetails.email": decoded.email }); - clientData = await ProjectModel.findById(projectId).populate("clientId"); - } else if (userType === "client") { - clientData = await ProjectModel.findById(projectId).populate("clientId"); - } - - if (clientData == null) return next(new Error("Invalid Project ID")); - const vaProjClientId = clientData.clientId._id; - - if (vaUser && vaUser.accessToken === token) { - socket.data.role = "va"; - socket.data.vaId = vaUser._id.toString(); - socket.data.vaEmail = decoded.email; - - const a = vaProjClientId.toString(); - const b = clientData.clientId._id.toString(); - const c = JSON.stringify(clientData); - - console.log(a, b, c, "vaProjClientId vs clientData.clientId"); - - vaSocketRespectToClient.set(vaProjClientId.toString(), socket.id) - - console.log("✅ Authenticated VA:", vaUser.userDetails.email); - return next(); - } - - /* ---------------------- 2⃣ Check if Client ---------------------- */ - // const clientUser = await ClientModel.findOne({ "userDetails.email": decoded.email }); - - // if (clientUser) { - // socket.data.role = "client"; - // socket.data.clientId = clientUser._id.toString(); - - // // Get all VA IDs assigned to this client - // socket.data.allowedVAs = clientUser.vaIds.map((va) => va.toString()); - - // console.log("✅ Authenticated Client:", clientUser.userDetails.email); - // return next(); - // } - const clientUser = clientData.clientId; - - if (clientUser) { - socket.data.role = "client"; - socket.data.clientId = clientUser._id.toString(); - - // Get all VA IDs assigned to this client - socket.data.allowedVAs = clientData.vaAssigned.map((va) => va.toString()); - - // console.log("✅ Authenticated Client:", clientUser.userDetails.email); - return next(); - } - - return next(new Error("Not Authorized: Invalid User")); - } catch (error) { - console.error("❌ Socket Auth Error:", error.message); - next(new Error("Authentication failed: " + error.message)); - } -}); - - - -io.on("connection", (socket) => { - console.log("User connected:", socket.id); - - // Register role and ID (client or VA) - socket.on("register", async ({ role, id, projectId = null, clientId = null }) => { - if (role === "va") { - socket.data.role = "va"; - socket.data.vaId = id; - console.log(`VA registered: ${id}`); - } - - if (role === "client") { - socket.data.role = "client"; - socket.data.vaId = id; - socket.data.projectId = projectId; - socket.data.clientId = null - - if (!projectId || !clientId) { - console.log("projectId is missing in register payload"); - return; - } - - socket.data.clientId = clientId; - - // 🧩 Get allowed VAs for this client - const client = await ClientModel.findById(clientId) - .populate("projectIds") // to access their projects - .populate("vaIds"); // optional if you track directly - - // Collect all VA IDs from their projects - const projectVAIds = new Set(); - for (const project of client.projectIds) { - const populated = await ProjectModel.findById(project._id).populate("vaAssigned"); - populated.vaAssigned.forEach((va) => projectVAIds.add(va._id.toString())); - } - - // Store allowed VA IDs in socket memory - socket.data.allowedVAs = Array.from(projectVAIds); - console.log(`Client ${id} can view VAs:`, socket.data.allowedVAs); - } - }); - - // When VA sends live image - socket.on("sendImage", async ({ token, image, title }) => { - try { - // Verify token - const decoded = jwt.verify(token, process.env.JWT_SECRET_ACCESS_TOKEN); - const vaEmail = decoded.email; - - // Find VA in DB - const vaUser = await VAModel.findOne({ "userDetails.email": vaEmail }); - if (!vaUser) { - console.log("❌ Unauthorized VA"); - return; - } - - const vaId = vaUser._id.toString(); - console.log("🟢 Received image from VA:", vaId); - - // Broadcast image only to allowed clients - for (let [_, clientSocket] of io.sockets.sockets) { - if ( - clientSocket.data.role === "client" && - clientSocket.data.allowedVAs?.includes(vaId) - ) { - clientSocket.emit("receiveImage", image, title); - } - } - } catch (err) { - console.error("❌ Token verification failed:", err.message); - } - }); - - socket.on("selectApp", ({ appTitle, clientId }) => { - console.log(`VA selected app: ${appTitle}`); - - const sssrrrr = vaSocketRespectToClient.get(clientId); - console.log(vaSocketRespectToClient.get(clientId), sssrrrr, "vaSocketRespectToClient[clientId]") - // You can forward this info to the respective Electron client - socket.to(vaSocketRespectToClient.get(clientId)).emit("appToCapture", { appTitle }); - }); - - socket.on("getVisibleApps", async ({ token, apps }) => { - try { - // Verify token - const decoded = jwt.verify(token, process.env.JWT_SECRET_ACCESS_TOKEN); - const vaEmail = decoded.email; - - // Find VA in DB - const vaUser = await VAModel.findOne({ "userDetails.email": vaEmail }); - if (!vaUser) { - console.log("❌ Unauthorized VA"); - return; - } - - const vaId = vaUser._id.toString(); - console.log("🟢 Received Apps from VA:", vaId); - - // Broadcast image only to allowed clients - for (let [_, clientSocket] of io.sockets.sockets) { - if ( - clientSocket.data.role === "client" && - clientSocket.data.allowedVAs?.includes(vaId) - ) { - clientSocket.emit("receiveVisibleApps", apps); - } - } - } catch (err) { - console.error("❌ Token verification failed:", err.message); - } - }); - - - socket.on('disconnect', () => { - console.log('a user Disconnected'); - }); -}); - - - - -// io.on('connection', (socket) => { - -// socket.on("sendImage", (base64String) => { -// io.emit("receiveImage", base64String); -// }); - - -// socket.on('disconnect', () => { -// console.log('a user Disconnected'); -// }); -// }); - -app.get("/client/login", (req, res) => { - res.render("client_login") -}) -app.get("/client/forgot-password", (req, res) => { - res.render("forgot_password") -}) - // Handling Socket io.use(async (socket, next) => { try { const token = socket.handshake.auth?.token; const projectId = socket.handshake.auth?.projectId; const userType = socket.handshake.auth?.userType; + const vaId = socket.handshake.auth?.vaId; + console.log("🔐 Received token:", token); @@ -464,7 +242,7 @@ io.use(async (socket, next) => { console.log(a, b, c, "vaProjClientId vs clientData.clientId"); - vaSocketRespectToClient.set(`${vaProjClientId.toString()}-${vaUser._id.toString()}`, socket.id) + vaSocketRespectToClient.set(`${vaProjClientId.toString()}-${vaUser._id.toString()}-${projectId.toString()}-va`, socket.id) console.log("✅ Authenticated VA:", vaUser.userDetails.email); return next(); @@ -491,6 +269,7 @@ io.use(async (socket, next) => { // Get all VA IDs assigned to this client socket.data.allowedVAs = clientData.vaAssigned.map((va) => va.toString()); + vaSocketRespectToClient.set(`${vaProjClientId.toString()}-${vaId.toString()}-${projectId.toString()}-client`, socket.id) // console.log("✅ Authenticated Client:", clientUser.userDetails.email); return next(); @@ -503,8 +282,6 @@ io.use(async (socket, next) => { } }); - - io.on("connection", (socket) => { console.log("User connected:", socket.id); @@ -539,7 +316,7 @@ io.on("connection", (socket) => { }); // When VA sends live image - socket.on("sendImage", async ({ token, image, title }) => { + socket.on("sendImage", async ({ token, image, title, projectId }) => { try { // Verify token const decoded = jwt.verify(token, process.env.JWT_SECRET_ACCESS_TOKEN); @@ -552,30 +329,42 @@ io.on("connection", (socket) => { return; } + const project = await ProjectModel.findById(projectId); + if (!project) { + console.log("❌ Invalid Project ID"); + return; + } + + const clientId = project.clientId.toString(); const vaId = vaUser._id.toString(); + const clientVaUniqueId = `${clientId}-${vaId}-${projectId}-client`; + const socketId = vaSocketRespectToClient.get(clientVaUniqueId); + console.log("🟢 Received image from VA:", vaId); // Broadcast image only to allowed clients - for (let [_, clientSocket] of io.sockets.sockets) { - if ( - clientSocket.data.role === "client" && - clientSocket.data.allowedVAs?.includes(vaId) - ) { - clientSocket.emit("receiveImage", image, title); - } - } + // for (let [_, clientSocket] of io.sockets.sockets) { + // if ( + // clientSocket.data.role === "client" && + // clientSocket.data.allowedVAs?.includes(vaId) + // ) { + // clientSocket.emit("receiveImage", image, title); + // } + // } + + // Can be like clientId-projectId-vaId to uniquely identify + socket.to(socketId).emit("receiveImage", image, title); } catch (err) { console.error("❌ Token verification failed:", err.message); } }); - socket.on("selectApp", ({ appTitle, clientId }) => { + socket.on("selectApp", ({ appTitle, clientId, vaId, projectId }) => { console.log(`VA selected app: ${appTitle}`); - const sssrrrr = vaSocketRespectToClient.get(`${clientId}-${socket.data.vaId}`); - console.log(vaSocketRespectToClient.get(`${clientId}-${socket.data.vaId}`), sssrrrr, "vaSocketRespectToClient[clientId]") + console.log(vaSocketRespectToClient.get(`${clientId}-${vaId}-${projectId}-va`), "vaSocketRespectToClient[clientId]") // You can forward this info to the respective Electron client - socket.to(vaSocketRespectToClient.get(`${clientId}-${socket.data.vaId}`)).emit("appToCapture", { appTitle }); + socket.to(vaSocketRespectToClient.get(`${clientId}-${vaId}-${projectId}-va`)).emit("appToCapture", { appTitle }); }); socket.on("getVisibleApps", async ({ token, apps }) => { @@ -600,7 +389,7 @@ io.on("connection", (socket) => { clientSocket.data.role === "client" && clientSocket.data.allowedVAs?.includes(vaId) ) { - clientSocket.emit("receiveVisibleApps", apps); + clientSocket.emit("receiveVisibleApps", {apps, vaId}); } } } catch (err) { @@ -614,37 +403,12 @@ io.on("connection", (socket) => { }); }); - - - -// io.on('connection', (socket) => { - -// socket.on("sendImage", (base64String) => { -// io.emit("receiveImage", base64String); -// }); - - -// socket.on('disconnect', () => { -// console.log('a user Disconnected'); -// }); -// }); - - - -// Express App Listening -// app.listen(PORT, () => { -// mongoose -// .connect( -// process.env.MONGO_URI, -// ) -// .then(() => console.log("mongodb connected")) -// .catch((err) => { -// console.log(err.message); -// process.exit(1); -// }); -// console.log(`Express started on http://localhost:${PORT}`); -// }); - +app.get("/client/login", (req, res) => { + res.render("client_login") +}) +app.get("/client/forgot-password", (req, res) => { + res.render("forgot_password") +}) app.use((req, res) => { res.status(404).render("not_found", { diff --git a/views/client/dashboard-dashboard.ejs b/views/client/dashboard-dashboard.ejs index 4fc3e5e..091dc6b 100644 --- a/views/client/dashboard-dashboard.ejs +++ b/views/client/dashboard-dashboard.ejs @@ -148,8 +148,7 @@ Status Virtual Assistant Projects Assigned - Working from - Total Worked + Worked Time Action @@ -222,7 +221,7 @@ // Format time function function formatTime(seconds) { - if (!seconds) return ''; + if (!seconds) return 'Not working'; const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); @@ -231,8 +230,6 @@ return `${hours}h ${minutes}m`; } else if (minutes > 0) { return `${minutes}m`; - } else if (seconds > 0) { - return `${seconds}s`; } else { return 'Just started'; } @@ -243,7 +240,6 @@ const statusClass = va.isCurrentlyActive ? 'active' : 'inactive'; const statusText = va.isCurrentlyActive ? 'Active' : 'Inactive'; const workedTime = formatTime(va.currentSessionDuration); - const totalWorkedTime = formatTime(va.totalWorkedSeconds); const projectsAssigned = va.projectsAssigned || 0; const viewLink = va.isCurrentlyActive ? ` @@ -251,7 +247,7 @@ ` : ''; return ` - +
@@ -260,7 +256,7 @@
-
${va.name}
+
${va.firstName} ${va.lastName}
${va.email || 'N/A'}
@@ -270,9 +266,6 @@ ${workedTime} - - ${totalWorkedTime} - ${viewLink} @@ -309,35 +302,16 @@ vaTableBody.innerHTML = allVAs.map(va => createVATableRow(va)).join(''); vaTableBody.style.display = ''; vaEmptyState.style.display = 'none'; - - // Add event listeners to table rows - document.querySelectorAll('tr[data-vaid]').forEach(row => { - row.addEventListener('click', async function(e) { - // Don't trigger if click is on view link - if (e.target.closest('.view-link')) { - return; - } - - const vaId = this.getAttribute('data-vaid'); - const vaData = allVAs.find(va => va.id === vaId); - - if (vaData) { - // Show loading state - const originalContent = this.innerHTML; - this.innerHTML = ' Loading...'; - - // Fetch additional VA details from /test API - const projectData = await fetchVADetails(vaId); - - // Restore original content - this.innerHTML = originalContent; - - // Show popup with VA details - showVAPopup(projectData); - } + + // Add event listeners to view links + document.querySelectorAll('.view-link').forEach(link => { + link.addEventListener('click', function(e) { + e.preventDefault(); + const vaId = this.getAttribute('data-va-id'); + // You can update this link as needed + window.location.href = `/client/virtual-assistants/${vaId}`; }); }); - } else { vaTableBody.innerHTML = ''; vaTableBody.style.display = 'none'; @@ -376,128 +350,7 @@ // Optional: Auto-refresh data every 30 seconds setInterval(loadDashboardData, 30000); - - function createPopup(vaData) { - // Extract project details from the API response - // The API response is the project array, so use it directly - const projectDetails = Array.isArray(vaData) ? vaData : (vaData.projects || []); - - // Create project details HTML - const projectsHTML = projectDetails.length > 0 - ? projectDetails.map(project => ` -
-
- ${project.projectName || 'Unknown Project'} - ${project.totalTime || '00:00:00'} -
-
ID: ${project.projectId || 'N/A'}
-
- `).join('') - : '
No project data available
'; - - return ` - - `; - } - - // Show popup with VA details - function showVAPopup(projectData) { - console.log('Project Data:', projectData); - - // Remove existing popup if any - const existingPopup = document.getElementById('vaPopup'); - if (existingPopup) { - existingPopup.remove(); - } - - // Create and show new popup with only project data - const popupHTML = createPopup(projectData); - document.body.insertAdjacentHTML('beforeend', popupHTML); - - // Add event listeners for closing popup - document.getElementById('closePopup').addEventListener('click', closePopup); - document.getElementById('closePopupBtn').addEventListener('click', closePopup); - - // Close popup when clicking outside - document.getElementById('vaPopup').addEventListener('click', function(e) { - if (e.target === this) { - closePopup(); - } - }); - - // Close popup with Escape key - document.addEventListener('keydown', function(e) { - if (e.key === 'Escape') { - closePopup(); - } - }); - } - - // Fetch VA details for popup - now returns only project data - async function fetchVADetails(vaId) { - try { - const response = await fetchWithAuth(`${API_BASE}/dashboard/va-report`, { - method: 'POST', - body: JSON.stringify({ vaId: vaId }), - headers: { - 'Content-Type': 'application/json' - } - }); - - if (!response) return; - - const result = await response.json(); - console.log('API Response:', result); - - if (result.success) { - // Return the projects array directly - return result.projects || []; - } else { - console.error('Failed to fetch project details:', result.message); - return []; - } - } catch (error) { - console.error('Error fetching project details:', error); - return []; - } - } - - // Close popup function - function closePopup() { - const popup = document.getElementById('vaPopup'); - if (popup) { - popup.remove(); - } - } - + diff --git a/views/va_live_tracker.ejs b/views/va_live_tracker.ejs index 52c7473..83dbf23 100644 --- a/views/va_live_tracker.ejs +++ b/views/va_live_tracker.ejs @@ -189,13 +189,15 @@