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

Total Credits

+
$0
+
+
+ +
+
+
+
+
+
+

Available Credits

+
$0
+
+
+ +
+
+
+
+ +
+
+
+ + + + + + + + + + + + +
StatusDateDescriptionAmount
+
+ +
+
\ 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 @@ -
  • +
  • Projects @@ -33,6 +33,13 @@
  • +
  • + + + Credits + +
  • +
  • diff --git a/views/va_register.ejs b/views/va_register.ejs index 83c76cd..01aeaa7 100644 --- a/views/va_register.ejs +++ b/views/va_register.ejs @@ -248,11 +248,11 @@
    - - + +
    @@ -281,11 +281,11 @@
    - - + +