12-08/2025 - va regiseter client register forget password layout fixed
This commit is contained in:
parent
5d952b04cf
commit
604afe9b72
|
|
@ -55,26 +55,50 @@ async function render_reset_form(req, res) {
|
|||
try {
|
||||
const { token, userType } = req.query;
|
||||
|
||||
// ❌ Missing params
|
||||
if (!token || !userType) {
|
||||
return res.render('auth/reset-password-error', { error: 'Reset token or user type is missing', userType: userType || 'va' });
|
||||
return res.render('auth/reset-password-error', {
|
||||
title: "Reset Password",
|
||||
layout: 'layouts/auth',
|
||||
error: 'Reset token or user type is missing',
|
||||
userType: userType || 'va'
|
||||
});
|
||||
}
|
||||
|
||||
// Validate token
|
||||
// ✅ Validate token
|
||||
const validation = await passwordResetService.validateResetToken(token, userType);
|
||||
|
||||
if (!validation.valid) {
|
||||
return res.render('auth/reset-password-error', { error: validation.message, userType: userType || 'va' });
|
||||
return res.render('auth/reset-password-error', {
|
||||
title: "Reset Password",
|
||||
layout: 'layouts/auth',
|
||||
error: validation.message,
|
||||
userType: userType || 'va'
|
||||
});
|
||||
}
|
||||
|
||||
// Render reset form with token
|
||||
res.render('auth/reset-password-form', { token, userType, error: null });
|
||||
// ✅ Render reset password form
|
||||
return res.render('auth/reset-password-form', {
|
||||
title: "Reset Password",
|
||||
layout: 'layouts/auth',
|
||||
token,
|
||||
userType,
|
||||
error: null
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`PasswordResetController - Render form error: ${error.message}`);
|
||||
res.render('auth/reset-password-error', { error: 'Failed to load reset form' });
|
||||
|
||||
return res.render('auth/reset-password-error', {
|
||||
title: "Reset Password",
|
||||
layout: 'layouts/auth',
|
||||
error: 'Failed to load reset form',
|
||||
userType: 'va'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Process password reset form submission (shared for both VA and Client)
|
||||
async function process_reset(req, res) {
|
||||
try {
|
||||
|
|
|
|||
159
src/server.js
159
src/server.js
|
|
@ -65,7 +65,9 @@ app.use('/api', apiRoutes);
|
|||
|
||||
app.get("/download-tracker", (req, res) => {
|
||||
res.render("download_tracker", {
|
||||
version: "3.2.1"
|
||||
title: 'Download tracker',
|
||||
version: "3.2.1",
|
||||
layout: 'layouts/main'
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -80,42 +82,64 @@ app.get("/download-file", (req, res) => {
|
|||
});
|
||||
|
||||
app.get('/varegister/:sf_token?', (req, res) => {
|
||||
const { sf_token } = req.params;
|
||||
const { sf_token } = req.params;
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(sf_token, process.env.JWT_SECRET_SF_TOKEN);
|
||||
// No token → invalid request
|
||||
if (!sf_token) {
|
||||
return res.status(404).render('not_found', {
|
||||
title: 'Page Not Found',
|
||||
layout: 'layouts/error'
|
||||
});
|
||||
}
|
||||
|
||||
if (decoded) {
|
||||
const email = decoded.email;
|
||||
res.render('va_register', { title: "VA Registration", email });
|
||||
} else {
|
||||
res.render('not_found', { title: "VA Registration" });
|
||||
}
|
||||
} catch (error) {
|
||||
res.render('not_found', { title: "VA Registration" });
|
||||
}
|
||||
try {
|
||||
const decoded = jwt.verify(sf_token, process.env.JWT_SECRET_SF_TOKEN);
|
||||
|
||||
// if(verified) {
|
||||
// res.render('va_register', { title: "VA Registration" });
|
||||
// } else {
|
||||
// res.render('not_founf', { title: "VA Registration" });
|
||||
// }
|
||||
// Token valid
|
||||
return res.render('va_register', {
|
||||
title: 'VA Registration',
|
||||
email: decoded.email,
|
||||
layout: 'layouts/varegisterlayout'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
// Invalid / expired token
|
||||
return res.status(401).render('not_found', {
|
||||
title: 'Invalid or Expired Link',
|
||||
layout: 'layouts/error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/clientregister/:sf_token?', (req, res) => {
|
||||
const { sf_token } = req.params;
|
||||
try {
|
||||
const decoded = jwt.verify(sf_token, process.env.JWT_SECRET_SF_TOKEN);
|
||||
|
||||
if (decoded) {
|
||||
const email = decoded.email;
|
||||
res.render('va_client_register', { title: "Client Registration", email });
|
||||
} else {
|
||||
res.render('not_found', { title: "Client Registration" });
|
||||
}
|
||||
} catch (error) {
|
||||
res.render('not_found', { title: "Client Registration" });
|
||||
}
|
||||
app.get('/clientregister/:sf_token?', (req, res) => {
|
||||
const { sf_token } = req.params;
|
||||
|
||||
// No token → invalid request
|
||||
if (!sf_token) {
|
||||
return res.status(404).render('not_found', {
|
||||
title: 'Page Not Found',
|
||||
layout: 'layouts/error'
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(sf_token, process.env.JWT_SECRET_SF_TOKEN);
|
||||
|
||||
// Token valid
|
||||
return res.render('va_client_register', {
|
||||
title: 'Client Registration',
|
||||
email: decoded.email,
|
||||
layout: 'layouts/vaclientlayout'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
// Invalid / expired token
|
||||
return res.status(401).render('not_found', {
|
||||
title: 'Invalid or Expired Link',
|
||||
layout: 'layouts/error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/auth/password/reset-form', render_reset_form);
|
||||
|
|
@ -135,7 +159,11 @@ app.get("/client/login", (req, res) => {
|
|||
});
|
||||
|
||||
app.get("/client/forgot-password", (req, res) => {
|
||||
res.render("forgot_password")
|
||||
|
||||
res.render("forgot_password", {
|
||||
title: "Forgot Password",
|
||||
layout: "layouts/forgotpasswordlayout"
|
||||
});
|
||||
})
|
||||
|
||||
// Client Dashboard Routes with new file names
|
||||
|
|
@ -208,7 +236,7 @@ app.get("/live/:projectId/:vaId", async (req, res) => {
|
|||
const { projectId, vaId } = req.params;
|
||||
|
||||
if (!projectId || !vaId) {
|
||||
return res.status(400).send({ "message": "Page not found" });
|
||||
return res.status(400).render("not_found", { title: "Page not found" });
|
||||
}
|
||||
|
||||
let clientId = null;
|
||||
|
|
@ -219,36 +247,45 @@ app.get("/live/:projectId/:vaId", async (req, res) => {
|
|||
}
|
||||
} catch (err) {
|
||||
console.error("Error fetching client for project:", err.message);
|
||||
return;
|
||||
return res.status(500).render("not_found", { title: "Server Error" });
|
||||
}
|
||||
|
||||
if (!clientId) return res.status(400).send({ "message": "Page not found" });
|
||||
|
||||
res.render("va_live_tracker", { title: "VA Live Tracker", projectId, vaId, clientId })
|
||||
})
|
||||
|
||||
app.get("/live/:projectId/:vaId", async (req, res) => {
|
||||
const { projectId, vaId } = req.params;
|
||||
|
||||
if (!projectId || !vaId) {
|
||||
return res.status(400).send({ "message": "Page not found" });
|
||||
if (!clientId) {
|
||||
return res.status(400).render("not_found", { title: "Page not found" });
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
res.render("va_live_tracker", {
|
||||
title: "VA Live Tracker",
|
||||
projectId,
|
||||
vaId,
|
||||
clientId,
|
||||
layout: "layouts/valivetrackerlayout" // ✅ ADD THIS
|
||||
});
|
||||
});
|
||||
|
||||
if (!clientId) return res.status(400).send({ "message": "Page not found" });
|
||||
|
||||
res.render("va_live_tracker", { title: "VA Live Tracker", projectId, vaId, clientId })
|
||||
})
|
||||
// app.get("/live/:projectId/:vaId", async (req, res) => {
|
||||
// const { projectId, vaId } = req.params;
|
||||
|
||||
// if (!projectId || !vaId) {
|
||||
// return res.status(400).send({ "message": "Page not found" });
|
||||
// }
|
||||
|
||||
// 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
|
||||
import { VAModel, ProjectModel, ClientModel } from './models/vaModel.js';
|
||||
|
|
@ -511,11 +548,13 @@ io.on("connection", (socket) => {
|
|||
});
|
||||
|
||||
app.use((req, res) => {
|
||||
res.status(404).render("not_found", {
|
||||
title: "404 - Page Not Found"
|
||||
});
|
||||
res.status(404).render("not_found", {
|
||||
layout: "layouts/error",
|
||||
title: "404 - Page Not Found"
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
server.listen(PORT, () => {
|
||||
mongoose
|
||||
.connect(
|
||||
|
|
|
|||
|
|
@ -1,121 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>DocVA | Reset Password</title>
|
||||
|
||||
<style>
|
||||
:root[data-theme="light"] {
|
||||
--bg: #ffffff;
|
||||
--text: #111827;
|
||||
--card-bg: #ffffff;
|
||||
--border: #e5e7eb;
|
||||
--btn-bg: #0a0a8f;
|
||||
--btn-text: #ffffff;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
--bg: #121212;
|
||||
--text: #f9fafb;
|
||||
--card-bg: #1e1e1e;
|
||||
--border: #334155;
|
||||
--btn-bg: #3b82f6;
|
||||
--btn-text: #ffffff;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: "Inter", sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
.container {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 170px;
|
||||
margin-bottom: 1.5rem;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
transition: background 0.3s, border-color 0.3s;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
input {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 0.8rem;
|
||||
margin-bottom: 1.2rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
background: #fff;
|
||||
color: #111;
|
||||
font-size: 0.9rem;
|
||||
transition: border 0.3s;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] input {
|
||||
background: #0f172a;
|
||||
color: #fff;
|
||||
border: 1px solid #475569;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
background: var(--btn-bg);
|
||||
color: var(--btn-text);
|
||||
border: none;
|
||||
padding: 0.8rem;
|
||||
border-radius: 8px;
|
||||
font-size: 0.95rem;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
margin-top: 1rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
opacity: 1;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2>DocTracker</h2>
|
||||
<form class="card">
|
||||
|
|
@ -200,6 +83,3 @@
|
|||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
<body>
|
||||
<%- body %>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
<!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>
|
||||
|
||||
<%- body %>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>DocVA | Reset Password</title>
|
||||
|
||||
<style>
|
||||
:root[data-theme="light"] {
|
||||
--bg: #ffffff;
|
||||
--text: #111827;
|
||||
--card-bg: #ffffff;
|
||||
--border: #e5e7eb;
|
||||
--btn-bg: #0a0a8f;
|
||||
--btn-text: #ffffff;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
--bg: #121212;
|
||||
--text: #f9fafb;
|
||||
--card-bg: #1e1e1e;
|
||||
--border: #334155;
|
||||
--btn-bg: #3b82f6;
|
||||
--btn-text: #ffffff;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: "Inter", sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
.container {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 170px;
|
||||
margin-bottom: 1.5rem;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
transition: background 0.3s, border-color 0.3s;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
input {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 0.8rem;
|
||||
margin-bottom: 1.2rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
background: #fff;
|
||||
color: #111;
|
||||
font-size: 0.9rem;
|
||||
transition: border 0.3s;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] input {
|
||||
background: #0f172a;
|
||||
color: #fff;
|
||||
border: 1px solid #475569;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
background: var(--btn-bg);
|
||||
color: var(--btn-text);
|
||||
border: none;
|
||||
padding: 0.8rem;
|
||||
border-radius: 8px;
|
||||
font-size: 0.95rem;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
margin-top: 1rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
opacity: 1;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%- body %>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -13,5 +13,7 @@
|
|||
</head>
|
||||
<body>
|
||||
<%- body %>
|
||||
|
||||
<script src="/public/js/common/theme.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Client Registration</title>
|
||||
|
||||
<link href="/public/css/va-register.css" rel="stylesheet">
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%- body %>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Live Screen Feed</title>
|
||||
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
background: #0f0f0f;
|
||||
color: white;
|
||||
font-family: "Inter", sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* SIDEBAR */
|
||||
.sidebar {
|
||||
width: 280px;
|
||||
background: #181818;
|
||||
border-right: 1px solid #2b2b2b;
|
||||
padding: 15px;
|
||||
overflow-y: auto;
|
||||
transition: transform 0.35s ease;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.sidebar h3 {
|
||||
margin-bottom: 15px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.app {
|
||||
background: #252525;
|
||||
border: 1px solid #333;
|
||||
padding: 12px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: 0.3s ease;
|
||||
}
|
||||
|
||||
.app:hover {
|
||||
background: #333;
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
.app.active {
|
||||
background: #6c63ff;
|
||||
border-color: #6c63ff;
|
||||
color: white;
|
||||
box-shadow: 0 0 10px rgba(108, 99, 255, 0.5);
|
||||
}
|
||||
|
||||
/* MAIN CONTENT */
|
||||
.content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#selectedAppTitle {
|
||||
font-size: 20px;
|
||||
margin-bottom: 15px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#liveImage {
|
||||
width: 90%;
|
||||
max-width: 900px;
|
||||
border-radius: 10px;
|
||||
border: 2px solid #444;
|
||||
object-fit: contain;
|
||||
background: black;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
font-size: 16px;
|
||||
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>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%- body %>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Virtual Assistant Registration</title>
|
||||
|
||||
<link href="/public/css/va-register.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
select option:disabled {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%- body %>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,105 +1,10 @@
|
|||
<!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>
|
||||
<a href="/client/login" class="btn">Go Home</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
@ -124,6 +29,3 @@
|
|||
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", applyTheme);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,16 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Client Registration</title>
|
||||
|
||||
<link href="/public/css/va-register.css" rel="stylesheet">
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="container">
|
||||
<!-- Sidebar for desktop -->
|
||||
<div class="sidebar">
|
||||
<div class="logo">
|
||||
|
|
@ -21,7 +9,7 @@
|
|||
<nav class="progress-steps">
|
||||
<div class="step active" data-step="1" onclick="goToStep(1)">
|
||||
<div class="step-number">1</div>
|
||||
<!-- <div class="step-number">✓</div> -->
|
||||
<!-- <div class="step-number">✓</div> -->
|
||||
<div class="step-text">Create Your Account</div>
|
||||
<div class="step-connector"></div>
|
||||
</div>
|
||||
|
|
@ -59,7 +47,7 @@
|
|||
<ol>
|
||||
<li class="step active" data-step="1" onclick="goToStep(1)">
|
||||
<div class="step-number">1</div>
|
||||
<!-- <div class="step-number">✓</div> -->
|
||||
<!-- <div class="step-number">✓</div> -->
|
||||
<div class="step-text">Create Account</div>
|
||||
</li>
|
||||
<li class="step" data-step="2" onclick="goToStep(2)">
|
||||
|
|
@ -98,16 +86,20 @@
|
|||
<form id="account-form">
|
||||
<div class="form-group">
|
||||
<div class="input-container">
|
||||
<input type="text" id="company-name" class="input-field" placeholder=" " oninput="validateCompanyName(this)" required>
|
||||
<input type="text" id="company-name" class="input-field" placeholder=" "
|
||||
oninput="validateCompanyName(this)" required>
|
||||
<label for="company-name" class="input-label">Company / Client Name *</label>
|
||||
</div>
|
||||
<div class="invalid-feedback" id="company-name-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
Company name should only contain letters, numbers, spaces, hyphens, apostrophes, ampersands, and periods
|
||||
<div class="invalid-feedback" id="company-name-error"
|
||||
style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
Company name should only contain letters, numbers, spaces, hyphens, apostrophes,
|
||||
ampersands, and periods
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-container">
|
||||
<input type="email" id="email" class="input-field" placeholder=" " value="<%= email %>" readonly disabled required>
|
||||
<input type="email" id="email" class="input-field" placeholder=" " value="<%= email %>"
|
||||
readonly disabled required>
|
||||
<label for="email" class="input-label">Email *</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -133,17 +125,20 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-container">
|
||||
<input type="text" id="city" class="input-field" placeholder=" " oninput="validateCity(this)" onkeypress="return preventInvalidCityChars(event)" required>
|
||||
<input type="text" id="city" class="input-field" placeholder=" "
|
||||
oninput="validateCity(this)" onkeypress="return preventInvalidCityChars(event)"
|
||||
required>
|
||||
<label for="city" class="input-label">City *</label>
|
||||
</div>
|
||||
<div class="invalid-feedback" id="city-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
<div class="invalid-feedback" id="city-error"
|
||||
style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
City name should contain letters and cannot be only numbers or special characters
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-container">
|
||||
<!-- <input type="text" id="state" class="input-field" placeholder=" " required> -->
|
||||
<select id="state" class="select-field" required>
|
||||
<select id="state" class="select-field" required>
|
||||
<option value="" disable selected style="display: none"></option>
|
||||
<!-- States will be populated by JavaScript -->
|
||||
</select>
|
||||
|
|
@ -153,10 +148,13 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-container">
|
||||
<input type="text" id="zip" class="input-field" placeholder=" " oninput="validateZipCode(this)" onkeypress="return preventNonNumericZip(event)" maxlength="10" required>
|
||||
<input type="text" id="zip" class="input-field" placeholder=" "
|
||||
oninput="validateZipCode(this)" onkeypress="return preventNonNumericZip(event)"
|
||||
maxlength="10" required>
|
||||
<label for="zip" class="input-label">ZIP *</label>
|
||||
</div>
|
||||
<div class="invalid-feedback" id="zip-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
<div class="invalid-feedback" id="zip-error"
|
||||
style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
Invalid Zip code
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -192,17 +190,20 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-container">
|
||||
<input type="text" id="billing-city" class="input-field" placeholder=" " oninput="validateCity(this)" onkeypress="return preventInvalidCityChars(event)" required>
|
||||
<input type="text" id="billing-city" class="input-field" placeholder=" "
|
||||
oninput="validateCity(this)" onkeypress="return preventInvalidCityChars(event)"
|
||||
required>
|
||||
<label for="billing-city" class="input-label">City *</label>
|
||||
</div>
|
||||
<div class="invalid-feedback" id="billing-city-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
<div class="invalid-feedback" id="billing-city-error"
|
||||
style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
City name should contain letters and cannot be only numbers or special characters
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-container">
|
||||
<!-- <input type="text" id="billing-state" class="input-field" placeholder=" " required> -->
|
||||
<select id="billing-state" class="select-field" required>
|
||||
<select id="billing-state" class="select-field" required>
|
||||
<option value="" disable selected style="display: none"></option>
|
||||
<!-- States will be populated by JavaScript -->
|
||||
</select>
|
||||
|
|
@ -211,10 +212,13 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-container">
|
||||
<input type="text" id="billing-zip" class="input-field" placeholder=" " oninput="validateZipCode(this)" onkeypress="return preventNonNumericZip(event)" maxlength="10" required>
|
||||
<input type="text" id="billing-zip" class="input-field" placeholder=" "
|
||||
oninput="validateZipCode(this)" onkeypress="return preventNonNumericZip(event)"
|
||||
maxlength="10" required>
|
||||
<label for="billing-zip" class="input-label">ZIP *</label>
|
||||
</div>
|
||||
<div class="invalid-feedback" id="billing-zip-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
<div class="invalid-feedback" id="billing-zip-error"
|
||||
style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
ZIP code should contain only numbers
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -242,24 +246,32 @@
|
|||
<div class="error-message" id="step-4-error-message"></div>
|
||||
</div>
|
||||
<p style="margin-bottom: 20px; color: #6b7280; font-size: 14px;">
|
||||
We will use the details below in the agreement that we will send to your prior to entering the project.
|
||||
We will use the details below in the agreement that we will send to your prior to entering the
|
||||
project.
|
||||
</p>
|
||||
<form id="signatory-form">
|
||||
<div class="form-group">
|
||||
<div class="input-container">
|
||||
<input type="text" id="position" class="input-field" placeholder=" " oninput="validatePosition(this)" onkeypress="return preventInvalidPositionChars(event)" required>
|
||||
<label for="position" class="input-label">Position (Owner / CEO / Manager / etc.) *</label>
|
||||
<input type="text" id="position" class="input-field" placeholder=" "
|
||||
oninput="validatePosition(this)"
|
||||
onkeypress="return preventInvalidPositionChars(event)" required>
|
||||
<label for="position" class="input-label">Position (Owner / CEO / Manager / etc.)
|
||||
*</label>
|
||||
</div>
|
||||
<div class="invalid-feedback" id="position-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
<div class="invalid-feedback" id="position-error"
|
||||
style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
Position should only contain letters, spaces, and common punctuation
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-container">
|
||||
<input type="text" id="signatory-name" class="input-field" placeholder=" " oninput="validateSignatoryName(this)" onkeypress="return preventNumbers(event)" required>
|
||||
<input type="text" id="signatory-name" class="input-field" placeholder=" "
|
||||
oninput="validateSignatoryName(this)" onkeypress="return preventNumbers(event)"
|
||||
required>
|
||||
<label for="signatory-name" class="input-label">Name *</label>
|
||||
</div>
|
||||
<div class="invalid-feedback" id="signatory-name-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
<div class="invalid-feedback" id="signatory-name-error"
|
||||
style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
Name should not contain numbers
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -297,26 +309,33 @@
|
|||
<div class="row">
|
||||
<div class="form-group">
|
||||
<div class="input-container">
|
||||
<input type="text" id="expiry-date" class="input-field" placeholder=" " required>
|
||||
<input type="text" id="expiry-date" class="input-field" placeholder=" "
|
||||
required>
|
||||
<label for="expiry-date" class="input-label">Expiry Date (MM/YY) *</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-container">
|
||||
<input type="text" id="security-code" class="input-field" placeholder=" " oninput="validateCVVVV(this)" nkeypress="return preventInvalidCVVChars(event)" maxlength="4" required>
|
||||
<input type="text" id="security-code" class="input-field" placeholder=" "
|
||||
oninput="validateCVVVV(this)"
|
||||
nkeypress="return preventInvalidCVVChars(event)" maxlength="4" required>
|
||||
<label for="security-code" class="input-label">Security Code *</label>
|
||||
</div>
|
||||
<div class="invalid-feedback" id="security-code-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
<div class="invalid-feedback" id="security-code-error"
|
||||
style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
Please enter a valid security code
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-container">
|
||||
<input type="text" id="card-zip" class="input-field" placeholder=" " oninput="validateZipCode(this)" onkeypress="return preventNonNumericZip(event)" maxlength="10" required>
|
||||
<input type="text" id="card-zip" class="input-field" placeholder=" "
|
||||
oninput="validateZipCode(this)" onkeypress="return preventNonNumericZip(event)"
|
||||
maxlength="10" required>
|
||||
<label for="card-zip" class="input-label">ZIP Code *</label>
|
||||
</div>
|
||||
<div class="invalid-feedback" id="card-zip-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
<div class="invalid-feedback" id="card-zip-error"
|
||||
style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
ZIP code should contain only numbers
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -328,19 +347,25 @@
|
|||
<div id="bank-details" class="payment-details">
|
||||
<div class="form-group">
|
||||
<div class="input-container">
|
||||
<input type="text" id="bank-name" class="input-field" placeholder=" " oninput="validateBankName(this)" onkeypress="return preventInvalidBankNameChars(event)" required>
|
||||
<input type="text" id="bank-name" class="input-field" placeholder=" "
|
||||
oninput="validateBankName(this)"
|
||||
onkeypress="return preventInvalidBankNameChars(event)" required>
|
||||
<label for="bank-name" class="input-label">Bank Name *</label>
|
||||
</div>
|
||||
<div class="invalid-feedback" id="bank-name-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
<div class="invalid-feedback" id="bank-name-error"
|
||||
style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
Bank name should contain letters and cannot be only numbers or special characters
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-container">
|
||||
<input type="text" id="account-number" class="input-field" placeholder=" " oninput="validateAccountNumber(this)" onkeypress="return preventInvalidAccountNumberChars(event)" required>
|
||||
<input type="text" id="account-number" class="input-field" placeholder=" "
|
||||
oninput="validateAccountNumber(this)"
|
||||
onkeypress="return preventInvalidAccountNumberChars(event)" required>
|
||||
<label for="account-number" class="input-label">Account Number *</label>
|
||||
</div>
|
||||
<div class="invalid-feedback" id="account-number-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
<div class="invalid-feedback" id="account-number-error"
|
||||
style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
Please enter a valid account number
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -362,10 +387,13 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-container">
|
||||
<input type="text" id="account-holder-name" class="input-field" placeholder=" " oninput="validateAccountHolderName(this)" onkeypress="return preventInvalidAccountHolderChars(event)" required>
|
||||
<input type="text" id="account-holder-name" class="input-field" placeholder=" "
|
||||
oninput="validateAccountHolderName(this)"
|
||||
onkeypress="return preventInvalidAccountHolderChars(event)" required>
|
||||
<label for="account-holder-name" class="input-label">Account Holder Name *</label>
|
||||
</div>
|
||||
<div class="invalid-feedback" id="account-holder-name-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
<div class="invalid-feedback" id="account-holder-name-error"
|
||||
style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||
Account holder name should only contain letters and spaces
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -382,15 +410,16 @@
|
|||
<div class="success-message">
|
||||
<div class="success-icon">✓</div>
|
||||
<h3>Registration Complete!</h3>
|
||||
<p>Your account has been successfully created. You will receive a confirmation email shortly.</p>
|
||||
<p>Your account has been successfully created. You will receive a confirmation email shortly.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/public/js/va-client-register.js"></script>
|
||||
<script>
|
||||
<script src="/public/js/va-client-register.js"></script>
|
||||
<script>
|
||||
function setTheme(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
localStorage.setItem('theme', theme);
|
||||
|
|
@ -403,6 +432,4 @@
|
|||
// Initialize theme
|
||||
const savedTheme = getSystemTheme();
|
||||
setTheme(savedTheme);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</script>
|
||||
|
|
@ -1,293 +1,124 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<!-- Mobile Toggle Button -->
|
||||
<div class="mobile-toggle" id="toggleBtn">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Live Screen Feed</title>
|
||||
<!-- Mobile Overlay -->
|
||||
<div id="overlay"></div>
|
||||
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
<div class="sidebar" id="sidebar">
|
||||
<h3>Visible Apps</h3>
|
||||
<div id="appsList"></div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<h2 id="selectedAppTitle">Select an app to view</h2>
|
||||
<img id="liveImage" alt="Live Feed" />
|
||||
<div id="placeholder" class="placeholder">No app selected</div>
|
||||
</div>
|
||||
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script>
|
||||
const vaId = "<%= vaId %>";
|
||||
const clientId = "<%= clientId %>";
|
||||
const projectId = "<%= projectId %>";
|
||||
const token = localStorage.getItem("knoptpq");
|
||||
|
||||
const socket = io({
|
||||
auth: {
|
||||
token: token,
|
||||
userType: "client",
|
||||
projectId: projectId,
|
||||
vaId: vaId
|
||||
},
|
||||
});
|
||||
|
||||
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 liveImage = document.getElementById("liveImage");
|
||||
const selectedAppTitle = document.getElementById("selectedAppTitle");
|
||||
const placeholder = document.getElementById("placeholder");
|
||||
|
||||
let currentApp = null;
|
||||
|
||||
/* MOBILE SIDEBAR TOGGLE */
|
||||
toggleBtn.onclick = () => {
|
||||
sidebar.classList.add("open");
|
||||
overlay.classList.add("show");
|
||||
};
|
||||
|
||||
overlay.onclick = () => {
|
||||
sidebar.classList.remove("open");
|
||||
overlay.classList.remove("show");
|
||||
};
|
||||
|
||||
socket.on("connected", () => {
|
||||
socket.emit("requestVisibleApps", { token });
|
||||
});
|
||||
|
||||
/* Receive visible apps */
|
||||
socket.on("receiveVisibleApps", ({ apps, vaId: senderVaId }) => {
|
||||
if (vaId !== senderVaId) return;
|
||||
|
||||
appsList.innerHTML = "";
|
||||
|
||||
if (!apps?.length) {
|
||||
appsList.innerHTML = "<p style='color:#777;'>No visible apps</p>";
|
||||
return;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
background: #0f0f0f;
|
||||
color: white;
|
||||
font-family: "Inter", sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
apps.forEach(app => {
|
||||
const div = document.createElement("div");
|
||||
div.classList.add("app");
|
||||
div.textContent = app.name || "Unnamed App";
|
||||
|
||||
/* SIDEBAR */
|
||||
.sidebar {
|
||||
width: 280px;
|
||||
background: #181818;
|
||||
border-right: 1px solid #2b2b2b;
|
||||
padding: 15px;
|
||||
overflow-y: auto;
|
||||
transition: transform 0.35s ease;
|
||||
z-index: 50;
|
||||
}
|
||||
div.onclick = () => {
|
||||
document.querySelectorAll(".app").forEach(el => el.classList.remove("active"));
|
||||
div.classList.add("active");
|
||||
|
||||
.sidebar h3 {
|
||||
margin-bottom: 15px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
currentApp = app.name;
|
||||
selectedAppTitle.textContent = app.name;
|
||||
|
||||
.app {
|
||||
background: #252525;
|
||||
border: 1px solid #333;
|
||||
padding: 12px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: 0.3s ease;
|
||||
}
|
||||
|
||||
.app:hover {
|
||||
background: #333;
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
.app.active {
|
||||
background: #6c63ff;
|
||||
border-color: #6c63ff;
|
||||
color: white;
|
||||
box-shadow: 0 0 10px rgba(108, 99, 255, 0.5);
|
||||
}
|
||||
|
||||
/* MAIN CONTENT */
|
||||
.content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#selectedAppTitle {
|
||||
font-size: 20px;
|
||||
margin-bottom: 15px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#liveImage {
|
||||
width: 90%;
|
||||
max-width: 900px;
|
||||
border-radius: 10px;
|
||||
border: 2px solid #444;
|
||||
object-fit: contain;
|
||||
background: black;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
font-size: 16px;
|
||||
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>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- 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>
|
||||
<div id="appsList"></div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<h2 id="selectedAppTitle">Select an app to view</h2>
|
||||
<img id="liveImage" alt="Live Feed" />
|
||||
<div id="placeholder" class="placeholder">No app selected</div>
|
||||
</div>
|
||||
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script>
|
||||
const vaId = "<%= vaId %>";
|
||||
const clientId = "<%= clientId %>";
|
||||
const projectId = "<%= projectId %>";
|
||||
const token = localStorage.getItem("knoptpq");
|
||||
|
||||
const socket = io({
|
||||
auth: {
|
||||
token: token,
|
||||
userType: "client",
|
||||
projectId: projectId,
|
||||
vaId: vaId
|
||||
},
|
||||
});
|
||||
|
||||
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 liveImage = document.getElementById("liveImage");
|
||||
const selectedAppTitle = document.getElementById("selectedAppTitle");
|
||||
const placeholder = document.getElementById("placeholder");
|
||||
|
||||
let currentApp = null;
|
||||
|
||||
/* MOBILE SIDEBAR TOGGLE */
|
||||
toggleBtn.onclick = () => {
|
||||
sidebar.classList.add("open");
|
||||
overlay.classList.add("show");
|
||||
};
|
||||
|
||||
overlay.onclick = () => {
|
||||
sidebar.classList.remove("open");
|
||||
overlay.classList.remove("show");
|
||||
};
|
||||
|
||||
socket.on("connected", () => {
|
||||
socket.emit("requestVisibleApps", { token });
|
||||
});
|
||||
|
||||
/* Receive visible apps */
|
||||
socket.on("receiveVisibleApps", ({ apps, vaId: senderVaId }) => {
|
||||
if (vaId !== senderVaId) return;
|
||||
|
||||
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, projectId });
|
||||
|
||||
// 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";
|
||||
}
|
||||
liveImage.style.display = "none";
|
||||
|
||||
socket.emit("selectApp", { appTitle: app.name, clientId, vaId, projectId });
|
||||
|
||||
// Close sidebar on mobile
|
||||
sidebar.classList.remove("open");
|
||||
overlay.classList.remove("show");
|
||||
};
|
||||
|
||||
appsList.appendChild(div);
|
||||
});
|
||||
</script>
|
||||
});
|
||||
|
||||
</body>
|
||||
socket.on("authError", () => {
|
||||
socket.disconnect();
|
||||
localStorage.removeItem("knoptpq");
|
||||
window.location.href = "/client/login";
|
||||
});
|
||||
|
||||
</html>
|
||||
/* Receive images */
|
||||
socket.on("receiveImage", (base64, title) => {
|
||||
if (currentApp && title === currentApp) {
|
||||
liveImage.src = base64;
|
||||
liveImage.style.display = "block";
|
||||
placeholder.style.display = "none";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
@ -1,20 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Virtual Assistant Registration</title>
|
||||
|
||||
<link href="/public/css/va-register.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
select option:disabled {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<!-- Sidebar for desktop -->
|
||||
|
|
@ -340,5 +324,4 @@
|
|||
const savedTheme = getSystemTheme();
|
||||
setTheme(savedTheme);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue