Client Register code update
This commit is contained in:
parent
acb1061de5
commit
36bdc24b54
Binary file not shown.
|
After Width: | Height: | Size: 504 B |
Binary file not shown.
|
After Width: | Height: | Size: 782 B |
Binary file not shown.
|
After Width: | Height: | Size: 255 B |
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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" });
|
||||||
|
} else {
|
||||||
|
res.render('not_found', { title: "VA Registration" });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
res.render('not_found', { title: "VA Registration" });
|
||||||
|
}
|
||||||
|
|
||||||
res.render('va_register', { 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(
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -3,144 +3,282 @@
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>Live Screen Feed</title>
|
<title>Live Screen Feed</title>
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
<style>
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: flex;
|
padding: 0;
|
||||||
height: 100vh;
|
|
||||||
background: #111;
|
|
||||||
color: #fff;
|
|
||||||
font-family: 'Inter', sans-serif;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
background: #0f0f0f;
|
||||||
|
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: {
|
||||||
token: token,
|
token: token,
|
||||||
userType: "client",
|
userType: "client",
|
||||||
projectId: "<%= projectId %>"
|
projectId: "<%= projectId %>"
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.emit("register", { role: "client", id: clientId, projectId: "<%= projectId %>" });
|
socket.emit("register", {
|
||||||
|
role: "client",
|
||||||
|
id: vaId,
|
||||||
|
projectId: "<%= projectId %>",
|
||||||
|
clientId: clientId
|
||||||
|
});
|
||||||
|
|
||||||
const appsList = document.getElementById("appsList");
|
const sidebar = document.getElementById("sidebar");
|
||||||
const liveImage = document.getElementById("liveImage");
|
const toggleBtn = document.getElementById("toggleBtn");
|
||||||
const selectedAppTitle = document.getElementById("selectedAppTitle");
|
const overlay = document.getElementById("overlay");
|
||||||
let currentApp = null;
|
|
||||||
|
|
||||||
// 🟢 Receive visible apps from client
|
const appsList = document.getElementById("appsList");
|
||||||
socket.on("receiveVisibleApps", (apps) => {
|
const liveImage = document.getElementById("liveImage");
|
||||||
appsList.innerHTML = "";
|
const selectedAppTitle = document.getElementById("selectedAppTitle");
|
||||||
if (!apps?.length) {
|
const placeholder = document.getElementById("placeholder");
|
||||||
appsList.innerHTML = "<p>No visible apps</p>";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
apps.forEach(app => {
|
let currentApp = null;
|
||||||
const div = document.createElement("div");
|
|
||||||
div.classList.add("app");
|
|
||||||
div.textContent = app.name || "Unnamed App";
|
|
||||||
div.onclick = () => {
|
|
||||||
document.querySelectorAll(".app").forEach(el => el.classList.remove("active"));
|
|
||||||
div.classList.add("active");
|
|
||||||
currentApp = app.name;
|
|
||||||
selectedAppTitle.textContent = app.name;
|
|
||||||
|
|
||||||
// 🟠 Tell server which app VA wants to monitor
|
/* MOBILE SIDEBAR TOGGLE */
|
||||||
socket.emit("selectApp", { appTitle: app.name, clientId });
|
toggleBtn.onclick = () => {
|
||||||
};
|
sidebar.classList.add("open");
|
||||||
appsList.appendChild(div);
|
overlay.classList.add("show");
|
||||||
});
|
};
|
||||||
});
|
|
||||||
|
|
||||||
// 🟣 Receive images only for the selected app
|
overlay.onclick = () => {
|
||||||
socket.on("receiveImage", (base64, title) => {
|
sidebar.classList.remove("open");
|
||||||
if (currentApp && title === currentApp) {
|
overlay.classList.remove("show");
|
||||||
liveImage.src = base64;
|
};
|
||||||
liveImage.classList.remove("no-image");
|
|
||||||
}
|
/* Receive visible apps */
|
||||||
});
|
socket.on("receiveVisibleApps", (apps) => {
|
||||||
|
appsList.innerHTML = "";
|
||||||
|
|
||||||
|
if (!apps?.length) {
|
||||||
|
appsList.innerHTML = "<p style='color:#777;'>No visible apps</p>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
apps.forEach(app => {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.classList.add("app");
|
||||||
|
div.textContent = app.name || "Unnamed App";
|
||||||
|
|
||||||
|
div.onclick = () => {
|
||||||
|
document.querySelectorAll(".app").forEach(el => el.classList.remove("active"));
|
||||||
|
div.classList.add("active");
|
||||||
|
|
||||||
|
currentApp = app.name;
|
||||||
|
selectedAppTitle.textContent = app.name;
|
||||||
|
|
||||||
|
placeholder.style.display = "none";
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("authError", () => {
|
||||||
|
socket.disconnect();
|
||||||
|
localStorage.removeItem("knoptpq");
|
||||||
|
window.location.href = "/client/login";
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Receive images */
|
||||||
|
socket.on("receiveImage", (base64, title) => {
|
||||||
|
if (currentApp && title === currentApp) {
|
||||||
|
liveImage.src = base64;
|
||||||
|
liveImage.style.display = "block";
|
||||||
|
placeholder.style.display = "none";
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Loading…
Reference in New Issue