Client Register code update

This commit is contained in:
=bhagwanSingh 2025-11-19 17:21:20 +05:30
parent acb1061de5
commit 36bdc24b54
8 changed files with 596 additions and 93 deletions

BIN
public/images/downloads.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 782 B

BIN
public/images/windows.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

View File

@ -594,7 +594,7 @@
nextStep(5); nextStep(5);
setTimeout(() => { setTimeout(() => {
window.location.href = '/login'; window.location.href = '/download-tracker';
}, 1000); }, 1000);
} else { } else {
// Handle server errors // Handle server errors

View File

@ -16,6 +16,7 @@ import { fileURLToPath } from 'url';
import apiRoutes from './routes/index.js' import apiRoutes from './routes/index.js'
import { render_reset_form } from "./controllers/passwordReset.controllers.js"; import { render_reset_form } from "./controllers/passwordReset.controllers.js";
import jwt from "jsonwebtoken";
const app = express(); const app = express();
@ -53,10 +54,38 @@ app.use(bodyParser.text());
// API Route // API Route
app.use('/api', apiRoutes); app.use('/api', apiRoutes);
app.get('/varegister/:sf_token?', (req, res) => { app.get("/download-tracker", (req, res) => {
// const { sf_token } = useParams(); res.render("download_tracker", {
version: "3.2.1"
});
});
app.get("/download-file", (req, res) => {
const os = req.query.os || "windows";
if (os === "windows") {
return res.download("./files/docTracker-3.2.1.exe");
} else {
return res.download("./files/docTracker-3.2.1.dmg");
}
});
app.get('/varegister/:sf_token?', (req, res) => {
const { sf_token } = req.params;
try {
const decoded = jwt.verify(sf_token, process.env.JWT_SECRET_SF_TOKEN);
if (decoded) {
res.render('va_register', { title: "VA Registration" }); res.render('va_register', { title: "VA Registration" });
} else {
res.render('not_found', { title: "VA Registration" });
}
} catch (error) {
res.render('not_found', { title: "VA Registration" });
}
// if(verified) { // if(verified) {
// res.render('va_register', { title: "VA Registration" }); // res.render('va_register', { title: "VA Registration" });
// } else { // } else {
@ -137,14 +166,27 @@ app.get('/client/virtual-assistants', async (req, res) => {
// } // }
// }); // });
app.get("/live/:projectId/:clientId", (req, res) => { app.get("/live/:projectId/:vaId", async (req, res) => {
const { projectId, clientId } = req.params; const { projectId, vaId } = req.params;
if(!projectId || !clientId){ if (!projectId || !vaId) {
return res.status(400).send({"message": "Page not found"}); return res.status(400).send({ "message": "Page not found" });
} }
res.render("va_live_tracker", { title: "VA Live Tracker", projectId, clientId }) let clientId = null;
try {
const clientRes = await ProjectModel.findById(projectId);
if (clientRes) {
clientId = clientRes.clientId.toString();
}
} catch (err) {
console.error("Error fetching client for project:", err.message);
return;
}
if (!clientId) return res.status(400).send({ "message": "Page not found" });
res.render("va_live_tracker", { title: "VA Live Tracker", projectId, vaId, clientId })
}) })
// Handling Socket // Handling Socket
@ -242,7 +284,7 @@ io.on("connection", (socket) => {
console.log("User connected:", socket.id); console.log("User connected:", socket.id);
// Register role and ID (client or VA) // Register role and ID (client or VA)
socket.on("register", async ({ role, id }) => { socket.on("register", async ({ role, id, projectId = null, clientId = null }) => {
if (role === "va") { if (role === "va") {
socket.data.role = "va"; socket.data.role = "va";
socket.data.vaId = id; socket.data.vaId = id;
@ -251,10 +293,19 @@ io.on("connection", (socket) => {
if (role === "client") { if (role === "client") {
socket.data.role = "client"; socket.data.role = "client";
socket.data.clientId = id; 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 // 🧩 Get allowed VAs for this client
const client = await ClientModel.findById(id) const client = await ClientModel.findById(clientId)
.populate("projectIds") // to access their projects .populate("projectIds") // to access their projects
.populate("vaIds"); // optional if you track directly .populate("vaIds"); // optional if you track directly
@ -370,8 +421,6 @@ app.get("/client/forgot-password", (req, res) => {
}) })
// Handling Socket // Handling Socket
import jwt from "jsonwebtoken";
io.use(async (socket, next) => { io.use(async (socket, next) => {
try { try {
const token = socket.handshake.auth?.token; const token = socket.handshake.auth?.token;
@ -415,7 +464,7 @@ io.use(async (socket, next) => {
console.log(a, b, c, "vaProjClientId vs clientData.clientId"); console.log(a, b, c, "vaProjClientId vs clientData.clientId");
vaSocketRespectToClient.set(vaProjClientId.toString(), socket.id) vaSocketRespectToClient.set(`${vaProjClientId.toString()}-${vaUser._id.toString()}`, socket.id)
console.log("✅ Authenticated VA:", vaUser.userDetails.email); console.log("✅ Authenticated VA:", vaUser.userDetails.email);
return next(); return next();
@ -523,10 +572,10 @@ io.on("connection", (socket) => {
socket.on("selectApp", ({ appTitle, clientId }) => { socket.on("selectApp", ({ appTitle, clientId }) => {
console.log(`VA selected app: ${appTitle}`); console.log(`VA selected app: ${appTitle}`);
const sssrrrr = vaSocketRespectToClient.get(clientId); const sssrrrr = vaSocketRespectToClient.get(`${clientId}-${socket.data.vaId}`);
console.log(vaSocketRespectToClient.get(clientId), sssrrrr, "vaSocketRespectToClient[clientId]") console.log(vaSocketRespectToClient.get(`${clientId}-${socket.data.vaId}`), sssrrrr, "vaSocketRespectToClient[clientId]")
// You can forward this info to the respective Electron client // You can forward this info to the respective Electron client
socket.to(vaSocketRespectToClient.get(clientId)).emit("appToCapture", { appTitle }); socket.to(vaSocketRespectToClient.get(`${clientId}-${socket.data.vaId}`)).emit("appToCapture", { appTitle });
}); });
socket.on("getVisibleApps", async ({ token, apps }) => { socket.on("getVisibleApps", async ({ token, apps }) => {
@ -596,6 +645,13 @@ io.on("connection", (socket) => {
// console.log(`Express started on http://localhost:${PORT}`); // console.log(`Express started on http://localhost:${PORT}`);
// }); // });
app.use((req, res) => {
res.status(404).render("not_found", {
title: "404 - Page Not Found"
});
});
server.listen(PORT, () => { server.listen(PORT, () => {
mongoose mongoose
.connect( .connect(

180
views/download_tracker.ejs Normal file
View File

@ -0,0 +1,180 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Download DocTracker</title>
<style>
body {
margin: 0;
font-family: Inter, sans-serif;
display: flex;
justify-content: center;
/* align-items: center; */
height: 100vh;
background: #ffffff;
color: #111;
padding: 20px;
text-align: center;
}
.container {
max-width: 630px;
}
h1 span {
color: #1a168b;
}
.desc {
font-size: 17px;
line-height: 1.6;
color: #333;
margin-bottom: 30px;
}
.os-select {
display: flex;
justify-content: center;
gap: 18px;
margin-bottom: 25px;
}
.os-btn {
padding: 12px 22px;
border-radius: 30px;
border: 2px solid #ddd;
cursor: pointer;
font-size: 15px;
background: white;
display: flex;
align-items: center;
gap: 8px;
transition: 0.2s;
}
.os-btn img {
width: 24px;
height: 24px;
}
.active img {
filter: brightness(0) invert(1);
}
.os-btn.active {
background: #0a0670;
color: #fff;
border-color: #0a0670;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
}
.download-btn {
margin: 0 auto;
padding: 14px 25px;
background: #d4d7f7;
color: #0a0670;
border: none;
border-radius: 10px;
font-size: 15px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
transition: 0.2s;
}
.download-btn img {
filter: brightness(0) invert(1);
width: 24px;
height: 24px;
}
#downloadBtn {
background: none;
border: none;
font-size: 15px;
color: inherit;
cursor: pointer;
}
.download-btn:hover {
background: #c5c8f0;
}
</style>
</head>
<body>
<div class="container">
<h1>Download <span>DocTracker</span></h1>
<p class="desc">
Let your clients see your real-time work logs, ensuring<br />
you get paid accurately for every minute you've put in.
</p>
<div class="os-select">
<div class="os-btn active" data-os="windows">
<img src="/public/images/windows.png" alt="windows">
<span>Windows</span>
</div>
<div class="os-btn" data-os="mac">
<img src="/public/images/mac-os-logo.png" alt="macos">
<span>MacOS</span>
</div>
</div>
<div class="download-btn">
<!-- <img src="/public/images/downloads.png" alt="macos" > -->
<button id="downloadBtn">
Download Windows version 3.2.1
</button>
</div>
</div>
<script>
const osButtons = document.querySelectorAll(".os-btn");
const downloadBtn = document.getElementById("downloadBtn");
const version = "<%= version || '3.2.1' %>";
let selectedOS = "windows";
// -------- Auto OS Detection --------
const platform = navigator.userAgent.toLowerCase();
if (platform.includes("mac")) selectedOS = "mac";
else selectedOS = "windows";
// Apply pre-selection
osButtons.forEach(btn => {
btn.classList.toggle("active", btn.dataset.os === selectedOS);
});
// Update button text
downloadBtn.innerHTML =
`Download ${selectedOS === 'windows' ? 'Windows' : 'MacOS'} version ${version}`;
// -------- Manual Selection by User --------
osButtons.forEach(btn => {
btn.addEventListener("click", () => {
osButtons.forEach(b => b.classList.remove("active"));
btn.classList.add("active");
selectedOS = btn.dataset.os;
downloadBtn.innerHTML =
`Download ${selectedOS === 'windows' ? 'Windows' : 'MacOS'} version ${version}`;
});
});
// -------- Download --------
downloadBtn.addEventListener("click", () => {
window.location.href = `/download-file?os=${selectedOS}`;
});
</script>
</body>
</html>

129
views/not_found.ejs Normal file
View File

@ -0,0 +1,129 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>404 - Page Not Found</title>
<style>
:root {
--bg: #ffffff;
--text: #222222;
--subtext: #555555;
--button-bg: #6c63ff;
--button-text: #ffffff;
}
/* Dark mode automatic */
@media (prefers-color-scheme: dark) {
:root {
--bg: #0f0f0f;
--text: #ffffff;
--subtext: #bbbbbb;
--button-bg: #6c63ff;
--button-text: #ffffff;
}
}
body {
margin: 0;
padding: 0;
background: var(--bg);
color: var(--text);
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
font-family: Inter, system-ui, sans-serif;
text-align: center;
padding: 20px;
}
.container {
max-width: 420px;
width: 100%;
}
h1 {
font-size: 7rem;
font-weight: 800;
margin: 0;
line-height: 1;
}
p {
font-size: 1.1rem;
margin-top: 10px;
color: var(--subtext);
}
.btn {
margin-top: 30px;
padding: 14px 26px;
background: var(--button-bg);
color: var(--button-text);
text-decoration: none;
font-weight: 600;
border-radius: 12px;
display: inline-block;
transition: 0.2s;
}
.btn:hover {
opacity: 0.85;
transform: translateY(-2px);
}
img {
width: 150px;
margin-bottom: 25px;
}
/* Responsive */
@media (max-width: 480px) {
h1 {
font-size: 4.5rem;
}
img {
width: 120px;
}
}
</style>
</head>
<body>
<div class="container">
<img src="/public/images/logo-white-vertical.svg" alt="404 Icon">
<h1>404</h1>
<p>Oops! The page you're looking for does not exist.</p>
<a href="/" class="btn">Go Home</a>
</div>
<script>
function applyTheme() {
const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
const logo = document.querySelector("img");
if (isDark) {
document.documentElement.classList.add("dark");
logo.src = "/public/images/logo-white-vertical.svg"; // Dark logo for light mode
} else {
document.documentElement.classList.remove("dark");
logo.src = "/public/images/logo-colored-vertical.svg"; // White logo for dark mode
}
}
// Apply theme on load
applyTheme();
// Listen for OS theme changes
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", applyTheme);
</script>
</body>
</html>

View File

@ -4,94 +4,192 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>Live Screen Feed</title> <title>Live Screen Feed</title>
<style> <style>
* { * {
box-sizing: border-box; box-sizing: border-box;
margin: 0;
padding: 0;
} }
body { body {
margin: 0;
display: flex;
height: 100vh; height: 100vh;
background: #111; display: flex;
color: #fff; background: #0f0f0f;
font-family: 'Inter', sans-serif; color: white;
font-family: "Inter", sans-serif;
overflow: hidden;
} }
/* SIDEBAR */
.sidebar { .sidebar {
width: 280px; width: 280px;
background: #1b1b1b; background: #181818;
border-right: 1px solid #333; border-right: 1px solid #2b2b2b;
padding: 15px;
overflow-y: auto; overflow-y: auto;
padding: 10px; transition: transform 0.35s ease;
z-index: 50;
}
.sidebar h3 {
margin-bottom: 15px;
font-size: 18px;
font-weight: 600;
} }
.app { .app {
background: #222; background: #252525;
border: 1px solid #333; border: 1px solid #333;
padding: 12px;
margin-bottom: 10px;
border-radius: 8px; border-radius: 8px;
padding: 10px 12px;
margin-bottom: 8px;
cursor: pointer; cursor: pointer;
transition: background 0.3s ease; transition: 0.3s ease;
word-break: break-word;
} }
.app:hover { .app:hover {
background: #333; background: #333;
transform: translateX(3px);
} }
.app.active { .app.active {
background: #6C63FF; background: #6c63ff;
border-color: #6c63ff;
color: white; color: white;
box-shadow: 0 0 10px rgba(108, 99, 255, 0.5);
} }
/* MAIN CONTENT */
.content { .content {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
overflow: hidden;
position: relative; position: relative;
padding: 20px;
overflow: hidden;
} }
h2 { #selectedAppTitle {
margin-bottom: 12px; font-size: 20px;
margin-bottom: 15px;
font-weight: 500; font-weight: 500;
text-align: center;
} }
img { #liveImage {
width: 90%; width: 90%;
max-width: 900px; max-width: 900px;
border: 2px solid #555;
border-radius: 10px; border-radius: 10px;
border: 2px solid #444;
object-fit: contain; object-fit: contain;
background: black;
display: none;
} }
.no-image { .placeholder {
font-size: 14px; font-size: 16px;
color: #888; opacity: 0.8;
color: #bbb;
margin-top: 20px;
}
/* MOBILE RESPONSIVE */
.mobile-toggle {
display: none;
position: fixed;
top: 15px;
left: 15px;
background: #6C63FF;
padding: 14px;
border-radius: 8px;
z-index: 9999;
/* VERY HIGH: always visible */
cursor: pointer;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
.mobile-toggle span {
display: block;
width: 26px;
height: 3px;
background: #fff;
margin: 5px 0;
border-radius: 2px;
}
/* Sidebar Hidden on Mobile */
@media (max-width: 780px) {
body {
flex-direction: column;
}
.sidebar {
position: fixed;
left: 0;
top: 0;
height: 100%;
transform: translateX(-100%);
}
.sidebar.open {
transform: translateX(0%);
}
.mobile-toggle {
display: block !important;
}
.content {
padding-top: 60px;
}
/* Dark overlay when sidebar opens */
#overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.45);
display: none;
z-index: 40;
}
#overlay.show {
display: block;
}
} }
</style> </style>
</head> </head>
<body> <body>
<div class="sidebar">
<!-- Mobile Toggle Button -->
<div class="mobile-toggle" id="toggleBtn">
<span></span>
<span></span>
<span></span>
</div>
<!-- Mobile Overlay -->
<div id="overlay"></div>
<div class="sidebar" id="sidebar">
<h3>Visible Apps</h3> <h3>Visible Apps</h3>
<div id="appsList"></div> <div id="appsList"></div>
</div> </div>
<div class="content"> <div class="content">
<h2 id="selectedAppTitle">Select an app to view</h2> <h2 id="selectedAppTitle">Select an app to view</h2>
<img id="liveImage" src="" alt="Waiting for image..." class="no-image" /> <img id="liveImage" alt="Live Feed" />
<div id="placeholder" class="placeholder">No app selected</div>
</div> </div>
<script src="/socket.io/socket.io.js"></script> <script src="/socket.io/socket.io.js"></script>
<script> <script>
const vaId = "<%= vaId %>";
const clientId = "<%= clientId %>"; const clientId = "<%= clientId %>";
const token = window.localStorage.getItem("knoptpq"); const token = localStorage.getItem("knoptpq");
const socket = io({ const socket = io({
auth: { auth: {
@ -101,18 +199,41 @@
}, },
}); });
socket.emit("register", { role: "client", id: clientId, projectId: "<%= projectId %>" }); socket.emit("register", {
role: "client",
id: vaId,
projectId: "<%= projectId %>",
clientId: clientId
});
const sidebar = document.getElementById("sidebar");
const toggleBtn = document.getElementById("toggleBtn");
const overlay = document.getElementById("overlay");
const appsList = document.getElementById("appsList"); const appsList = document.getElementById("appsList");
const liveImage = document.getElementById("liveImage"); const liveImage = document.getElementById("liveImage");
const selectedAppTitle = document.getElementById("selectedAppTitle"); const selectedAppTitle = document.getElementById("selectedAppTitle");
const placeholder = document.getElementById("placeholder");
let currentApp = null; let currentApp = null;
// 🟢 Receive visible apps from client /* MOBILE SIDEBAR TOGGLE */
toggleBtn.onclick = () => {
sidebar.classList.add("open");
overlay.classList.add("show");
};
overlay.onclick = () => {
sidebar.classList.remove("open");
overlay.classList.remove("show");
};
/* Receive visible apps */
socket.on("receiveVisibleApps", (apps) => { socket.on("receiveVisibleApps", (apps) => {
appsList.innerHTML = ""; appsList.innerHTML = "";
if (!apps?.length) { if (!apps?.length) {
appsList.innerHTML = "<p>No visible apps</p>"; appsList.innerHTML = "<p style='color:#777;'>No visible apps</p>";
return; return;
} }
@ -120,27 +241,44 @@
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("app"); div.classList.add("app");
div.textContent = app.name || "Unnamed App"; div.textContent = app.name || "Unnamed App";
div.onclick = () => { div.onclick = () => {
document.querySelectorAll(".app").forEach(el => el.classList.remove("active")); document.querySelectorAll(".app").forEach(el => el.classList.remove("active"));
div.classList.add("active"); div.classList.add("active");
currentApp = app.name; currentApp = app.name;
selectedAppTitle.textContent = app.name; selectedAppTitle.textContent = app.name;
// 🟠 Tell server which app VA wants to monitor placeholder.style.display = "none";
socket.emit("selectApp", { appTitle: app.name, clientId }); liveImage.style.display = "none";
socket.emit("selectApp", { appTitle: app.name, clientId, vaId });
// Close sidebar on mobile
sidebar.classList.remove("open");
overlay.classList.remove("show");
}; };
appsList.appendChild(div); appsList.appendChild(div);
}); });
}); });
// 🟣 Receive images only for the selected app socket.on("authError", () => {
socket.disconnect();
localStorage.removeItem("knoptpq");
window.location.href = "/client/login";
});
/* Receive images */
socket.on("receiveImage", (base64, title) => { socket.on("receiveImage", (base64, title) => {
if (currentApp && title === currentApp) { if (currentApp && title === currentApp) {
liveImage.src = base64; liveImage.src = base64;
liveImage.classList.remove("no-image"); liveImage.style.display = "block";
placeholder.style.display = "none";
} }
}); });
</script> </script>
</body> </body>
</html> </html>