Password Reset on register

This commit is contained in:
=bhagwanSingh 2025-12-12 12:30:40 +05:30
parent fc821c70ca
commit 9a811e6f00
3 changed files with 370 additions and 258 deletions

View File

@ -577,7 +577,7 @@ body {
.data-table tbody tr { .data-table tbody tr {
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
transition: background-color 0.2s; transition: background-color 0.2s;
cursor: pointer; /* cursor: pointer; */
} }
.data-table tbody tr:hover { .data-table tbody tr:hover {
@ -1688,4 +1688,24 @@ tr[data-vaid]:hover {
[data-theme="dark"] .loading { [data-theme="dark"] .loading {
opacity: 0.5; opacity: 0.5;
}
.va-action {
text-decoration: none;
color: #fff;
background-color: #2563eb;
padding: 0.5rem 1rem;
text-align: center;
border-radius: 5px;
transform: scale(0.96);
transition: all 0.2s ease-in-out;
cursor: pointer;
}
.va-info {
cursor: pointer;
}
.va-action:hover {
transform: scale(1);
} }

View File

@ -2,9 +2,22 @@ import VaClientRepository from "../../repository/vaClient.repository.js";
import VaClientService from "../../services/vaClient.service.js"; import VaClientService from "../../services/vaClient.service.js";
import logger from "../../config/logger.js"; import logger from "../../config/logger.js";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import PasswordResetService from "../../services/passwordReset.service.js";
import PasswordResetRepository from "../../repository/passwordReset.repository.js";
const vaClientService = new VaClientService(new VaClientRepository()); const vaClientService = new VaClientService(new VaClientRepository());
const passwordResetService = new PasswordResetService(
new PasswordResetRepository(),
// new EmailService({
// host: process.env.SMTP_HOST,
// port: process.env.SMTP_PORT,
// user: process.env.SMTP_USER,
// pass: process.env.SMTP_PASS,
// from: process.env.EMAIL_FROM,
// })
);
async function va_register_client(req, res) { async function va_register_client(req, res) {
logger.info("ClientController - Register Client Hit ..."); logger.info("ClientController - Register Client Hit ...");
@ -23,7 +36,7 @@ async function va_register_client(req, res) {
logger.info(`ClientController - Client registered successfully: ${userDetails.email}`); logger.info(`ClientController - Client registered successfully: ${userDetails.email}`);
await passwordResetService.requestPasswordReset(userDetails.email, 'client'); await passwordResetService.requestPasswordReset(userDetails.email, 'client');
logger.info(`Client password reset requested for: ${email}`); logger.info(`Client password reset requested for: ${userDetails.email}`);
return res.status(201).json({ return res.status(201).json({
success: true, success: true,

View File

@ -1,5 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" data-theme="light"> <html lang="en" data-theme="light">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
@ -7,6 +8,7 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link rel="stylesheet" href="/public/css/dashboard.css"> <link rel="stylesheet" href="/public/css/dashboard.css">
</head> </head>
<body class="dashboard-body"> <body class="dashboard-body">
<div id="root"> <div id="root">
<nav class="dashboard-header"> <nav class="dashboard-header">
@ -16,7 +18,7 @@
<h2 class="sidebar-title">DocVA Client Portal</h2> <h2 class="sidebar-title">DocVA Client Portal</h2>
</div> </div>
</div> </div>
<div class="header-nav"> <div class="header-nav">
<ul class="nav-list"> <ul class="nav-list">
<li class="nav-item"> <li class="nav-item">
@ -35,7 +37,7 @@
</a> </a>
</li> </li>
</ul> </ul>
<button class="mobile-menu-button"> <button class="mobile-menu-button">
<i class="fas fa-bars"></i> <i class="fas fa-bars"></i>
</button> </button>
@ -122,7 +124,8 @@
<!-- Virtual Assistants List --> <!-- Virtual Assistants List -->
<div class="content-section"> <div class="content-section">
<div class="section-header" style="justify-content: space-between;align-items: center;"> <div class="section-header" style="justify-content: space-between;align-items: center;">
<h2 class="section-title">Virtual Assistants <span class="total-count" id="vaListCount">0 VAs</span></h2> <h2 class="section-title">Virtual Assistants <span class="total-count" id="vaListCount">0
VAs</span></h2>
<a href="/client/virtual-assistants/manage-tips" class="btn btn-primary">Manage Tips</a> <a href="/client/virtual-assistants/manage-tips" class="btn btn-primary">Manage Tips</a>
</div> </div>
<div class="table-container"> <div class="table-container">
@ -155,6 +158,8 @@
</div> </div>
</main> </main>
</div> </div>
<!-- VA Modal Start -->
<div id="vaModal" class="modal-overlay"> <div id="vaModal" class="modal-overlay">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header" style="display: flex;align-items: center;justify-content: space-between;"> <div class="modal-header" style="display: flex;align-items: center;justify-content: space-between;">
@ -171,7 +176,7 @@
Projects Projects
<span class="va-count-badge" id="projectsCountBadge">0</span> <span class="va-count-badge" id="projectsCountBadge">0</span>
</h3> </h3>
<div class="va-table-container"> <div class="va-table-container">
<table class="va-table"> <table class="va-table">
<thead> <thead>
@ -204,7 +209,63 @@
</div> </div>
</div> </div>
</div> </div>
<!-- VA Modal Start -->
<!-- Log Modal Start -->
<div id="vaModal" class="modal-overlay">
<div class="modal-content">
<div class="modal-header" style="display: flex;align-items: center;justify-content: space-between;">
<h2 class="modal-title" id="modalVAName">VA Details</h2>
<button class="modal-close" onclick="closeVAModal()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="modal-body">
<!-- Projects Section -->
<div class="va-details-section">
<!-- <h3 class="va-section-title">
<i class="fas fa-briefcase"></i>
Projects
<span class="va-count-badge" id="projectsCountBadge">0</span>
</h3> -->
<div class="va-table-container">
<table class="va-table">
<thead>
<tr>
<th>#</th>
<th>Title</th>
<th>Time</th>
<th>Duration</th>
</tr>
</thead>
<tbody id="modalProjectsTableBody">
<!-- Project details will be loaded here -->
</tbody>
</table>
<div id="modalNoProjects" class="no-vas-message" style="display: none;">
<i class="fas fa-briefcase"></i>
<h4>No Logs Found</h4>
</div>
</div>
</div>
<!-- Action Button -->
<div class="modal-actions" id="modalActions" style="display: none;">
<a href="#" class="view-session-btn" id="viewSessionBtn" target="_blank">
<i class="fas fa-eye"></i>
View Live Session
</a>
</div>
</div>
</div>
</div>
<!-- Log Modal End -->
<div id="logoutModal" class="modal-overlay"> <div id="logoutModal" class="modal-overlay">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@ -218,7 +279,8 @@
<i class="fas fa-sign-out-alt"></i> <i class="fas fa-sign-out-alt"></i>
</div> </div>
<h3 class="logout-title">Are you sure you want to logout?</h3> <h3 class="logout-title">Are you sure you want to logout?</h3>
<p class="logout-message">You will be logged out of your account and redirected to the login page.</p> <p class="logout-message">You will be logged out of your account and redirected to the login page.
</p>
<div class="logout-actions"> <div class="logout-actions">
<button class="logout-cancel-btn" onclick="closeLogoutModal()">Cancel</button> <button class="logout-cancel-btn" onclick="closeLogoutModal()">Cancel</button>
<button class="logout-confirm-btn" id="confirmLogoutBtn">Yes, Logout</button> <button class="logout-confirm-btn" id="confirmLogoutBtn">Yes, Logout</button>
@ -227,137 +289,137 @@
</div> </div>
</div> </div>
</div> </div>
<script> <script>
// Theme management // Theme management
function setTheme(theme) { function setTheme(theme) {
document.documentElement.setAttribute('data-theme', theme); document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme); localStorage.setItem('theme', theme);
}
function getSystemTheme() {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
// Initialize theme
const savedTheme = getSystemTheme();
setTheme(savedTheme);
// API base URL
const API_BASE = '/api/v1/client';
// Auth token management
function getAuthToken() {
return localStorage.getItem('knoptpq');
}
async function fetchWithAuth(url, options = {}) {
const token = getAuthToken();
if (!token) {
window.location.href = '/';
return;
} }
const response = await fetch(url, { function getSystemTheme() {
...options, return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
headers: { }
'token': `${token}`,
'Content-Type': 'application/json', // Initialize theme
...options.headers, const savedTheme = getSystemTheme();
setTheme(savedTheme);
// API base URL
const API_BASE = '/api/v1/client';
// Auth token management
function getAuthToken() {
return localStorage.getItem('knoptpq');
}
async function fetchWithAuth(url, options = {}) {
const token = getAuthToken();
if (!token) {
window.location.href = '/';
return;
} }
});
if (response.status === 401) {
localStorage.removeItem('knoptpq');
localStorage.removeItem('refreshToken');
window.location.href = '/';
return;
}
return response;
}
async function handleLogout() { const response = await fetch(url, {
try { ...options,
// Call logout API headers: {
await fetchWithAuth(`${API_BASE}/logout`, { 'token': `${token}`,
method: 'POST' 'Content-Type': 'application/json',
...options.headers,
}
}); });
} catch (error) {
console.error('Logout API error:', error);
} finally {
// Clear local storage and redirect
localStorage.removeItem('knoptpq');
localStorage.removeItem('refreshToken');
window.location.href = '/';
}
}
// Format time function if (response.status === 401) {
function formatTime(seconds) { localStorage.removeItem('knoptpq');
if (!seconds) return 'No Record Found'; localStorage.removeItem('refreshToken');
window.location.href = '/';
const hours = Math.floor(seconds / 3600); return;
const minutes = Math.floor((seconds % 3600) / 60); }
if (hours > 0) {
return `${hours}h ${minutes}m`;
} else if (minutes > 0) {
return `${minutes}m`;
} else {
return 'Just started';
}
}
const vaModal = document.getElementById('vaModal'); return response;
function openVAModal(va) {
// Set basic VA info
document.getElementById('modalVAName').textContent = va.name;
// Set projects
const projects = va.projects || [];
document.getElementById('projectsCountBadge').textContent = projects.length;
const projectsTableBody = document.getElementById('modalProjectsTableBody');
const noProjectsMessage = document.getElementById('modalNoProjects');
if (projects.length > 0) {
projectsTableBody.innerHTML = projects.map(project => createProjectTableRow(project)).join('');
projectsTableBody.style.display = '';
noProjectsMessage.style.display = 'none';
} else {
projectsTableBody.innerHTML = '';
projectsTableBody.style.display = 'none';
noProjectsMessage.style.display = 'block';
} }
// Set action button async function handleLogout() {
const modalActions = document.getElementById('modalActions'); try {
const viewSessionBtn = document.getElementById('viewSessionBtn'); // Call logout API
await fetchWithAuth(`${API_BASE}/logout`, {
if (va.isCurrentlyActive && va.lastProjectId) { method: 'POST'
viewSessionBtn.href = `/live/${va.lastProjectId}/${va.id}`; });
modalActions.style.display = 'block'; } catch (error) {
} else { console.error('Logout API error:', error);
modalActions.style.display = 'none'; } finally {
// Clear local storage and redirect
localStorage.removeItem('knoptpq');
localStorage.removeItem('refreshToken');
window.location.href = '/';
}
} }
// Show modal // Format time function
vaModal.style.display = 'flex'; function formatTime(seconds) {
document.body.style.overflow = 'hidden'; if (!seconds) return '-';
}
function closeVAModal() { const hours = Math.floor(seconds / 3600);
vaModal.style.display = 'none'; const minutes = Math.floor((seconds % 3600) / 60);
document.body.style.overflow = 'auto';
}
// Create project table row for modal if (hours > 0) {
function createProjectTableRow(project) { return `${hours}h ${minutes}m`;
const statusClass = project.isCurrentlyWorking ? 'active' : 'inactive'; } else if (minutes > 0) {
const statusText = project.isCurrentlyWorking ? 'Working' : 'Not Active'; return `${minutes}m`;
} else {
return ` return 'Just started';
}
}
const vaModal = document.getElementById('vaModal');
function openVAModal(va) {
// Set basic VA info
document.getElementById('modalVAName').textContent = va.name;
// Set projects
const projects = va.projects || [];
document.getElementById('projectsCountBadge').textContent = projects.length;
const projectsTableBody = document.getElementById('modalProjectsTableBody');
const noProjectsMessage = document.getElementById('modalNoProjects');
if (projects.length > 0) {
projectsTableBody.innerHTML = projects.map(project => createProjectTableRow(project)).join('');
projectsTableBody.style.display = '';
noProjectsMessage.style.display = 'none';
} else {
projectsTableBody.innerHTML = '';
projectsTableBody.style.display = 'none';
noProjectsMessage.style.display = 'block';
}
// Set action button
const modalActions = document.getElementById('modalActions');
const viewSessionBtn = document.getElementById('viewSessionBtn');
if (va.isCurrentlyActive && va.lastProjectId) {
viewSessionBtn.href = `/live/${va.lastProjectId}/${va.id}`;
modalActions.style.display = 'block';
} else {
modalActions.style.display = 'none';
}
// Show modal
vaModal.style.display = 'flex';
document.body.style.overflow = 'hidden';
}
function closeVAModal() {
vaModal.style.display = 'none';
document.body.style.overflow = 'auto';
}
// Create project table row for modal
function createProjectTableRow(project) {
const statusClass = project.isCurrentlyWorking ? 'active' : 'inactive';
const statusText = project.isCurrentlyWorking ? 'Working' : 'Not Active';
return `
<tr> <tr>
<td> <td>
<div class="project-info"> <div class="project-info">
@ -377,18 +439,18 @@
</td> </td>
</tr> </tr>
`; `;
} }
// Create VA table row for main table // Create VA table row for main table
function createVATableRow(va) { function createVATableRow(va) {
const statusClass = va.isCurrentlyActive ? 'active' : 'inactive'; const statusClass = va.isCurrentlyActive ? 'active' : 'inactive';
const statusText = va.isCurrentlyActive ? 'Active' : 'Inactive'; const statusText = va.isCurrentlyActive ? 'Active' : 'Inactive';
const activeProject = va?.lastProject?.name ? va.lastProject.name : '-'; const activeProject = va?.lastProject?.name ? va.lastProject.name : '-';
const workedTime = formatTime(va.currentSessionDuration); const workedTime = formatTime(va.currentSessionDuration);
const projectsAssigned = va.projectsAssigned || 0; const projectsAssigned = va.projectsAssigned || 0;
const viewLink = va.isCurrentlyActive ? `<a href="/live/${va.lastProjectId}/${va.id}" class="view-link" data-va-id="${va.id}" target="_blank">Live Tracking</a>` : ''; const viewLink = va.isCurrentlyActive ? `<a href="/live/${va.lastProjectId}/${va.id}" class="view-link va-action" data-va-id="${va.id}" target="_blank">Live Tracking</a>` : '<p class="va-logs va-action" data-va-id="${va.id}" target="_blank">Logs</p>';
return ` return `
<tr class="va-row" data-va-id="${va.id}"> <tr class="va-row" data-va-id="${va.id}">
<td> <td>
<div class="status-indicator"> <div class="status-indicator">
@ -405,7 +467,7 @@
<span class="projects-count">${activeProject}</span> <span class="projects-count">${activeProject}</span>
</td> </td>
<td> <td>
<span class="projects-count">${projectsAssigned} Projects</span> <span class="projects-count">${projectsAssigned}</span>
</td> </td>
<td> <td>
<span class="worked-time">${workedTime}</span> <span class="worked-time">${workedTime}</span>
@ -415,144 +477,161 @@
</td> </td>
</tr> </tr>
`; `;
}
// Load dashboard data
async function loadDashboardData() {
try {
const response = await fetchWithAuth(`${API_BASE}/dashboard`);
if (!response) return;
const result = await response.json();
if (result.success) {
const data = result.data;
const summary = result.summary;
// Update stats cards
document.getElementById('activeVaCount').textContent = summary.activeVAs || 0;
document.getElementById('totalVaCount').textContent = summary.totalVAs || 0;
document.getElementById('totalProjects').textContent = summary.totalProjects || 0;
// Update VA list count
document.getElementById('vaListCount').textContent = `${summary.totalVAs || 0} VAs`;
// Update VA table
const vaTableBody = document.getElementById('vaTableBody');
const vaEmptyState = document.getElementById('vaEmptyState');
const allVAs = data.allVAs || [];
if (allVAs.length > 0) {
vaTableBody.innerHTML = allVAs.map(va => createVATableRow(va)).join('');
vaTableBody.style.display = '';
vaEmptyState.style.display = 'none';
// Store VA data globally for modal access
window.vaData = allVAs;
// Add click event listeners to VA rows
document.querySelectorAll('.va-row').forEach((row, index) => {
row.addEventListener('click', function(e) {
// Don't open modal if clicking on view link
if (!e.target.closest('.view-link')) {
const va = allVAs[index];
openVAModal(va);
}
});
});
} else {
vaTableBody.innerHTML = '';
vaTableBody.style.display = 'none';
vaEmptyState.style.display = 'block';
}
} else {
console.error('Failed to load dashboard data:', result.message);
showError('Failed to load dashboard data');
}
} catch (error) {
console.error('Error loading dashboard data:', error);
showError('Error loading dashboard data');
} }
}
// Show error message // Load dashboard data
function showError(message) { async function loadDashboardData() {
const vaTableBody = document.getElementById('vaTableBody'); try {
const vaEmptyState = document.getElementById('vaEmptyState'); const response = await fetchWithAuth(`${API_BASE}/dashboard`);
if (!response) return;
vaTableBody.style.display = 'none'; const result = await response.json();
vaEmptyState.innerHTML = `
if (result.success) {
const data = result.data;
const summary = result.summary;
// Update stats cards
document.getElementById('activeVaCount').textContent = summary.activeVAs || 0;
document.getElementById('totalVaCount').textContent = summary.totalVAs || 0;
document.getElementById('totalProjects').textContent = summary.totalProjects || 0;
// Update VA list count
document.getElementById('vaListCount').textContent = `${summary.totalVAs || 0} VAs`;
// Update VA table
const vaTableBody = document.getElementById('vaTableBody');
const vaEmptyState = document.getElementById('vaEmptyState');
const allVAs = data.allVAs || [];
if (allVAs.length > 0) {
vaTableBody.innerHTML = allVAs.map(va => createVATableRow(va)).join('');
vaTableBody.style.display = '';
vaEmptyState.style.display = 'none';
// Store VA data globally for modal access
window.vaData = allVAs;
// Add click event listeners to VA rows
document.querySelectorAll('.va-info').forEach((row, index) => {
row.addEventListener('click', function (e) {
// Don't open modal if clicking on view link
if (!e.target.closest('.view-link')) {
const va = allVAs[index];
openVAModal(va);
}
});
});
} else {
vaTableBody.innerHTML = '';
vaTableBody.style.display = 'none';
vaEmptyState.style.display = 'block';
}
} else {
console.error('Failed to load dashboard data:', result.message);
showError('Failed to load dashboard data');
}
} catch (error) {
console.error('Error loading dashboard data:', error);
showError('Error loading dashboard data');
}
}
// Show error message
function showError(message) {
const vaTableBody = document.getElementById('vaTableBody');
const vaEmptyState = document.getElementById('vaEmptyState');
vaTableBody.style.display = 'none';
vaEmptyState.innerHTML = `
<div class="empty-state"> <div class="empty-state">
<i class="fas fa-exclamation-triangle"></i> <i class="fas fa-exclamation-triangle"></i>
<h3>Error</h3> <h3>Error</h3>
<p>${message}</p> <p>${message}</p>
</div> </div>
`; `;
vaEmptyState.style.display = 'block'; vaEmptyState.style.display = 'block';
}
// Close modal when clicking outside
vaModal.addEventListener('click', (e) => {
if (e.target === vaModal) {
closeVAModal();
} }
});
// Close modal with escape key // Close modal when clicking outside
document.addEventListener('keydown', (e) => { vaModal.addEventListener('click', (e) => {
if (e.key === 'Escape' && vaModal.style.display === 'flex') { if (e.target === vaModal) {
closeVAModal(); closeVAModal();
} }
});
// Optional: Auto-refresh data every 30 seconds
setInterval(loadDashboardData, 30000);
// Logout Modal Functions
function openLogoutModal() {
const modal = document.getElementById('logoutModal');
modal.style.display = 'flex';
document.body.style.overflow = 'hidden';
}
function closeLogoutModal() {
const modal = document.getElementById('logoutModal');
modal.style.display = 'none';
document.body.style.overflow = 'auto';
}
document.addEventListener('DOMContentLoaded', function() {
// Load dashboard data
loadDashboardData();
// Mobile menu toggle
const menuToggle = document.querySelector('.mobile-menu-button');
const sidebar = document.querySelector('.dashboard-sidebar');
menuToggle.addEventListener('click', () => {
sidebar.classList.toggle('active');
}); });
// Close modal with escape key // Close modal with escape key
document.addEventListener('keydown', function(e) { document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') { if (e.key === 'Escape' && vaModal.style.display === 'flex') {
closeLogoutModal(); closeVAModal();
} }
}); });
document.getElementById('logoutModal').addEventListener('click', function(e) { // Optional: Auto-refresh data every 30 seconds
if (e.target === this) { setInterval(loadDashboardData, 30000);
closeLogoutModal();
// Logout Modal Functions
function openLogoutModal() {
const modal = document.getElementById('logoutModal');
modal.style.display = 'flex';
document.body.style.overflow = 'hidden';
}
function closeLogoutModal() {
const modal = document.getElementById('logoutModal');
modal.style.display = 'none';
document.body.style.overflow = 'auto';
}
const handleVaLogsClick = (e) => {
e.preventDefault();
cosnole.log("handleVaLogsClick Called")
}
document.addEventListener('DOMContentLoaded', function () {
// Load dashboard data
loadDashboardData();
// Mobile menu toggle
const menuToggle = document.querySelector('.mobile-menu-button');
const sidebar = document.querySelector('.dashboard-sidebar');
menuToggle.addEventListener('click', () => {
sidebar.classList.toggle('active');
});
// Close modal with escape key
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') {
closeLogoutModal();
}
});
document.getElementById('logoutModal').addEventListener('click', function (e) {
if (e.target === this) {
closeLogoutModal();
}
});
// Logout button - opens confirmation modal instead of directly logging out
document.getElementById('logoutButton').addEventListener('click', openLogoutModal);
// Confirm logout button
document.getElementById('confirmLogoutBtn').addEventListener('click', handleLogout);
const allVaLogsBtn = document.querySelectorAll(".va-logs");
for (const va of allVaLogsBtn) {
va.addEventListener("click", handleVaLogsClick)
} }
}); });
// Logout button - opens confirmation modal instead of directly logging out
document.getElementById('logoutButton').addEventListener('click', openLogoutModal);
// Confirm logout button
document.getElementById('confirmLogoutBtn').addEventListener('click', handleLogout);
});
</script>
</script>
</body> </body>
</html> </html>