Compare commits

..

2 Commits

14 changed files with 769 additions and 641 deletions

View File

@ -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 {

View File

@ -59,7 +59,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'
});
});
@ -76,39 +78,61 @@ app.get("/download-file", (req, res) => {
app.get('/varegister/: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);
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" });
}
// Token valid
return res.render('va_register', {
title: 'VA Registration',
email: decoded.email,
layout: 'layouts/varegisterlayout'
});
// if(verified) {
// res.render('va_register', { title: "VA Registration" });
// } else {
// res.render('not_founf', { title: "VA Registration" });
// }
} 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;
// 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);
if (decoded) {
const email = decoded.email;
res.render('va_client_register', { title: "Client Registration", email });
} else {
res.render('not_found', { title: "Client Registration" });
}
// Token valid
return res.render('va_client_register', {
title: 'Client Registration',
email: decoded.email,
layout: 'layouts/vaclientlayout'
});
} catch (error) {
res.render('not_found', { title: "Client Registration" });
// Invalid / expired token
return res.status(401).render('not_found', {
title: 'Invalid or Expired Link',
layout: 'layouts/error'
});
}
});
@ -119,7 +143,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
@ -184,11 +212,12 @@ app.get('/client/virtual-assistants/view-logs', async (req, res) => {
}
});
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;
@ -199,36 +228,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';
@ -523,11 +561,12 @@ app.post("/post-test", (req, res) => {
app.use((req, res) => {
res.status(404).render("not_found", {
layout: "layouts/error",
title: "404 - Page Not Found"
});
});
server.listen(PORT, "0.0.0.0", () => {
server.listen(PORT, () => {
mongoose
.connect(
process.env.MONGO_URI,

View File

@ -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>

18
views/layouts/auth.ejs Normal file
View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8">
<title>
<%= title || 'Auth' %>
</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="/public/images/favicon.svg" sizes="any">
</head>
<body>
<%- body %>
</body>
</html>

103
views/layouts/error.ejs Normal file
View File

@ -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>

View File

@ -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>

View File

@ -13,5 +13,7 @@
</head>
<body>
<%- body %>
<script src="/public/js/common/theme.js"></script>
</body>
</html>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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">
@ -116,7 +104,8 @@
</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>
@ -160,10 +149,13 @@
</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>
@ -180,10 +172,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>
@ -219,10 +214,13 @@
</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>
@ -238,10 +236,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>
@ -274,19 +275,26 @@
<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>
@ -324,26 +332,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>
@ -355,19 +370,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>
@ -389,10 +410,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>
@ -409,15 +433,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);
@ -430,6 +455,4 @@
// Initialize theme
const savedTheme = getSystemTheme();
setTheme(savedTheme);
</script>
</body>
</html>
</script>

View File

@ -1,191 +1,26 @@
<!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>
<!-- Mobile Toggle Button -->
<div class="mobile-toggle" id="toggleBtn">
<!-- Mobile Toggle Button -->
<div class="mobile-toggle" id="toggleBtn">
<span></span>
<span></span>
<span></span>
</div>
</div>
<!-- Mobile Overlay -->
<div id="overlay"></div>
<!-- Mobile Overlay -->
<div id="overlay"></div>
<div class="sidebar" id="sidebar">
<div class="sidebar" id="sidebar">
<h3>Visible Apps</h3>
<div id="appsList"></div>
</div>
</div>
<div class="content">
<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>
</div>
<script src="/socket.io/socket.io.js"></script>
<script>
<script src="/socket.io/socket.io.js"></script>
<script>
const vaId = "<%= vaId %>";
const clientId = "<%= clientId %>";
const projectId = "<%= projectId %>";
@ -275,7 +110,7 @@
socket.on("authError", () => {
socket.disconnect();
localStorage.removeItem("knoptpq");
window.location.href = "/";
window.location.href = "/client/login";
});
/* Receive images */
@ -286,8 +121,4 @@
placeholder.style.display = "none";
}
});
</script>
</body>
</html>
</script>

View File

@ -1,25 +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;
}
select {
background-color: white;
color: #2d2d2d;
}
</style>
</head>
<body>
<div class="container">
<!-- Sidebar for desktop -->
@ -346,5 +325,4 @@
const savedTheme = getSystemTheme();
setTheme(savedTheme);
</script>
</body>
</html>