diff --git a/public/css/dashboard.css b/public/css/dashboard.css
index 6879380..5c2814b 100644
--- a/public/css/dashboard.css
+++ b/public/css/dashboard.css
@@ -165,7 +165,6 @@ body {
}
.mobile-menu-button {
- display: none;
width: 2rem;
height: 2rem;
align-items: center;
@@ -201,6 +200,9 @@ body {
.dashboard-sidebar {
display: block;
}
+ .mobile-menu-button {
+ display: none;
+ }
}
@media (max-width: 768px) {
@@ -217,6 +219,10 @@ body {
.dashboard-sidebar.active {
transform: translateX(0);
}
+
+ .nav-list {
+ display: none;
+ }
}
.sidebar-header {
@@ -2203,4 +2209,20 @@ tr[data-vaid]:hover {
font-size: 12px;
transform: none;
color: var(--accent-light);
-}
\ No newline at end of file
+}
+
+.data-table #vaCreditTableBody .status{
+ text-transform: capitalize;
+ font-size: 14px;
+ font-weight: 500;
+ color: var(--text-primary);
+ padding: 4px 10px;
+ border-radius: 20px;
+ display: inline-block;
+ background-color: var(--color-gray-200);
+}
+
+.data-table #vaCreditTableBody .status.invoiced {
+ background-color: rgba(16, 185, 129, 0.1);
+ color: var(--color-success);
+}
diff --git a/public/js/common/header.js b/public/js/common/header.js
index 7939feb..8e05f8d 100644
--- a/public/js/common/header.js
+++ b/public/js/common/header.js
@@ -26,4 +26,14 @@ document.addEventListener('DOMContentLoaded', () => {
profileMenu.classList.remove('active');
}
});
+});
+
+
+document.addEventListener("DOMContentLoaded", () => {
+ const mobileMenuButton = document.querySelector(".mobile-menu-button");
+ const sidebar = document.querySelector(".dashboard-sidebar");
+ mobileMenuButton.addEventListener("click", () => {
+ sidebar.classList.toggle("active");
+ }
+ );
});
\ No newline at end of file
diff --git a/public/js/pages/va-credits.js b/public/js/pages/va-credits.js
new file mode 100644
index 0000000..645abc9
--- /dev/null
+++ b/public/js/pages/va-credits.js
@@ -0,0 +1,61 @@
+// API base URL
+const API_BASE = '/api/v1/client';
+
+const vaCreditsTableBody = document.getElementById("vaCreditTableBody");
+const vaCreditsEmptyState = document.getElementById("vaEmptyCreditState");
+
+function createCreditRow(credit) {
+ return `
+
+ | ${credit.status} |
+ ${credit.date} |
+ ${credit.comment} |
+ $${credit.creditAmount} |
+
+ `;
+}
+
+
+async function loadCreditsData() {
+ const res = await fetchWithAuth(`${API_BASE}/credits`);
+ if (!res) return;
+
+ const result = await res.json();
+ if (!result.success) return;
+ console.log("Credits Result:", result);
+
+ const { data } = result;
+
+ console.log("Credits Data:", data);
+
+ const allCredits = data || [];
+
+ if (allCredits.length === 0) {
+ vaCreditsTableBody.innerHTML = "";
+ vaCreditsEmptyState.style.display = "block";
+ return;
+ }
+
+ vaCreditsEmptyState.style.display = "none";
+
+ vaCreditsTableBody.innerHTML = allCredits
+ .map((credit) => createCreditRow(credit))
+ .join("");
+
+ let totalCredits = 0;
+ let availableCredits = 0;
+
+ allCredits.forEach(credit => {
+ totalCredits += parseFloat(credit.creditAmount);
+ if (credit.status.toLowerCase() === 'pending') {
+ availableCredits += parseFloat(credit.creditAmount);
+ }
+ });
+
+
+ document.getElementById("vaTotalCredits").textContent = `$${totalCredits}`;
+ document.getElementById("vaAvailableCredits").textContent = `$${availableCredits}`;
+}
+
+// Initial load
+loadCreditsData();
\ No newline at end of file
diff --git a/public/js/va-register.js b/public/js/va-register.js
index a6ecd35..c3a6358 100644
--- a/public/js/va-register.js
+++ b/public/js/va-register.js
@@ -621,12 +621,18 @@
if (!payoneerAccount) {
showError('Please enter your Payoneer account number', 'payoneerAccount', step);
isValid = false;
+ } else if (!validateEmail(payoneerAccount)) {
+ showError('Please enter a valid email address for Payoneer', 'payoneerAccount', step);
+ isValid = false;
}
} else if (paymentMethod === 'veem') {
const veemAccount = document.getElementById('veemAccount').value;
if (!veemAccount) {
showError('Please enter your Veem account number', 'veemAccount', step);
isValid = false;
+ } else if (!validateEmail(veemAccount)) {
+ showError('Please enter a valid email address for Veem', 'veemAccount', step);
+ isValid = false;
}
} else if (paymentMethod === 'wise') {
const wiseEmail = document.getElementById('wiseEmail').value;
@@ -763,6 +769,48 @@
return true;
}
+ function validateVeemEmail(input) {
+ const wiseError = document.getElementById('veem-error');
+ const value = input.value.trim();
+
+ // Remove any validation styling first
+ input.style.border = '';
+ wiseError.style.display = 'none';
+
+ if (value.length > 0) {
+ // Check if it's a valid email
+ if (!validateEmail(value)) {
+ wiseError.textContent = 'Please enter a valid email address for Veem';
+ wiseError.style.display = 'block';
+ input.style.border = '1px solid #ef4444';
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ function validatePayoneerEmail(input) {
+ const wiseError = document.getElementById('payoneer-error');
+ const value = input.value.trim();
+
+ // Remove any validation styling first
+ input.style.border = '';
+ wiseError.style.display = 'none';
+
+ if (value.length > 0) {
+ // Check if it's a valid email
+ if (!validateEmail(value)) {
+ wiseError.textContent = 'Please enter a valid email address for Payoneer';
+ wiseError.style.display = 'block';
+ input.style.border = '1px solid #ef4444';
+ return false;
+ }
+ }
+
+ return true;
+ }
+
// Function to generate a strong password
function generatePassword() {
const length = 12;
diff --git a/src/controllers/client/dashboard/clientDashboardCredits.controller.js b/src/controllers/client/dashboard/clientDashboardCredits.controller.js
new file mode 100644
index 0000000..f87e2b5
--- /dev/null
+++ b/src/controllers/client/dashboard/clientDashboardCredits.controller.js
@@ -0,0 +1,40 @@
+import authFetch from "../../../utils/authFetch.js";
+
+async function va_credits_get(req, res) {
+ try {
+ const clientId = req.body.clientId;
+ const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/credit/${clientId}`;
+
+ const response = await authFetch(salesforceUrl, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ // Add any required headers for Salesforce authentication
+ // 'Authorization': 'Bearer ' + process.env.SF_ACCESS_TOKEN
+ },
+ });
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ throw new Error(`Salesforce API responded with status ${response.status}: ${errorText}`);
+ }
+
+ const result = await response.json();
+ // console.log("✅ Successfully synced VA data to Salesforce:", result);
+
+ res.status(201).json({
+ success: true,
+ data: result.data
+ });
+ } catch (error) {
+ console.error("Failed to sync with Salesforce:", error.message);
+ console.log(error)
+
+ // Create a custom error to distinguish Salesforce errors
+ const salesforceError = new Error(`Salesforce sync failed: ${error.message}`);
+ salesforceError.isSalesforceError = true;
+ throw salesforceError;
+ }
+}
+
+export { va_credits_get };
\ No newline at end of file
diff --git a/src/routes/v1/client.routes.js b/src/routes/v1/client.routes.js
index 56b6ed1..c6b095c 100644
--- a/src/routes/v1/client.routes.js
+++ b/src/routes/v1/client.routes.js
@@ -15,6 +15,7 @@ import { client_dashboard_projects } from "../../controllers/client/dashboard/cl
import { client_logout_callback } from "../../controllers/client/dashboard/clientLogout.controller.js";
import { va_tips_delete, va_tips_get, va_tips_manage_function, va_tips_update } from "../../controllers/va/vaTips/vaTipsManage.controller.js";
import { clientDashboardGetVaLogsController } from "../../controllers/client/dashboard/clientDashboardGetVaLogs.controller.js";
+import { va_credits_get } from "../../controllers/client/dashboard/clientDashboardCredits.controller.js";
// clients routes
clientRoutes.post("/register", va_register_client); // Register a new VA
@@ -29,6 +30,8 @@ clientRoutes.get("/tips", clientAuthValidator, va_tips_get);
clientRoutes.patch("/tips/:tipId", clientAuthValidator, va_tips_update);
clientRoutes.delete("/tips/:tipId", clientAuthValidator, va_tips_delete);
+clientRoutes.get("/credits", clientAuthValidator, va_credits_get);
+
clientRoutes.get("/vaLogsData/:vaId", clientAuthValidator, clientDashboardGetVaLogsController);
// clientRoutes.get("/:clientID?", va_clients_callback); // VA Clients Callback for getting all VA's or specific client by ID (NC)
diff --git a/src/server.js b/src/server.js
index 3200028..07c6bc8 100644
--- a/src/server.js
+++ b/src/server.js
@@ -248,6 +248,19 @@ app.get('/client/virtual-assistants/view-logs', async (req, res) => {
}
});
+app.get('/client/credits', async (req, res) => {
+ try {
+ // res.render('client/dashboard-va-logs');
+ res.render('client/dashboard-credits', {
+ title: 'Credits',
+ pageScript: 'va-credits.js'
+ });
+ } catch (error) {
+ console.error('Projects error:', error);
+ res.status(500).render('client/dashboard-va-logs', { error: 'Failed to load profile' });
+ }
+});
+
app.get("/live/:projectId/:vaId", async (req, res) => {
const { projectId, vaId } = req.params;
diff --git a/views/client/dashboard-credits.ejs b/views/client/dashboard-credits.ejs
new file mode 100644
index 0000000..2d3a8d6
--- /dev/null
+++ b/views/client/dashboard-credits.ejs
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
Available Credits
+
$0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Status |
+ Date |
+ Description |
+ Amount |
+
+
+
+
+
+
+
+
+
+
+
No Credit Found
+
You haven't received any credit yet.
+
+
+
+
\ No newline at end of file
diff --git a/views/layouts/dashboard.ejs b/views/layouts/dashboard.ejs
index d166b59..5fad7b3 100644
--- a/views/layouts/dashboard.ejs
+++ b/views/layouts/dashboard.ejs
@@ -3,6 +3,7 @@
+
<%= title || 'Dashboard' %>
diff --git a/views/partials/sidebar.ejs b/views/partials/sidebar.ejs
index 84604a2..6d86a42 100644
--- a/views/partials/sidebar.ejs
+++ b/views/partials/sidebar.ejs
@@ -25,7 +25,7 @@
-
+
+
+
+
+