Compare commits
1 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
cf97a7a65b |
|
|
@ -1978,3 +1978,13 @@ tr[data-vaid]:hover {
|
||||||
transform: none;
|
transform: none;
|
||||||
color: var(--accent-light);
|
color: var(--accent-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.field-error {
|
||||||
|
border: 1px solid var(--error-color) !important;
|
||||||
|
background-color: rgba(220, 38, 38, 0.05) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .field-error {
|
||||||
|
background-color: rgba(220, 38, 38, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
import { ClientModel } from "../../../models/vaModel.js";
|
||||||
|
import authFetch from "../../../utils/authFetch.js";
|
||||||
|
|
||||||
|
async function update_client_bank_details(req, res) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
const clientId = req.body.clientId;
|
||||||
|
const clientEmail = req.body.clientEmail;
|
||||||
|
const payload = req.body || {};
|
||||||
|
const { userDetails } = payload;
|
||||||
|
|
||||||
|
const client = await ClientModel.findById(clientId);
|
||||||
|
if (!client) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: "Client not found"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userDetails) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: "Missing userDetails in request body"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
userDetails._id = clientId; // Ensure the _id is set to clientId
|
||||||
|
userDetails.email = clientEmail; // Ensure the email is set to clientEmail
|
||||||
|
|
||||||
|
// console.log("Final userDetails to update:", userDetails);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}/services/apexrest/docva/api/client`;
|
||||||
|
|
||||||
|
const response = await authFetch(salesforceUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ userDetails })
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: "Client bank details updated and synced with Salesforce successfully",
|
||||||
|
data: result
|
||||||
|
});
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
if (error.message === "Client not found") {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "Internal Server Error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { update_client_bank_details };
|
||||||
|
|
@ -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 { 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 { 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 { clientDashboardGetVaLogsController } from "../../controllers/client/dashboard/clientDashboardGetVaLogs.controller.js";
|
||||||
|
import { update_client_bank_details } from "../../controllers/client/dashboard/clientUpdateBankDetails.controller.js";
|
||||||
|
|
||||||
// clients routes
|
// clients routes
|
||||||
clientRoutes.post("/register", va_register_client); // Register a new VA
|
clientRoutes.post("/register", va_register_client); // Register a new VA
|
||||||
|
|
@ -40,6 +41,7 @@ clientRoutes.post("/dashboard/va-report", clientAuthValidator, client_dashboard_
|
||||||
clientRoutes.get("/dashboard-projects", clientAuthValidator, client_dashboard_projects); // VA Clients Projects Callback
|
clientRoutes.get("/dashboard-projects", clientAuthValidator, client_dashboard_projects); // VA Clients Projects Callback
|
||||||
clientRoutes.get("/profile", clientAuthValidator, get_client_dashboard_profile);
|
clientRoutes.get("/profile", clientAuthValidator, get_client_dashboard_profile);
|
||||||
clientRoutes.patch("/profile", clientAuthValidator, update_client_password);
|
clientRoutes.patch("/profile", clientAuthValidator, update_client_password);
|
||||||
|
clientRoutes.post("/profile", clientAuthValidator, update_client_bank_details);
|
||||||
clientRoutes.patch("/:clientId", update_client);
|
clientRoutes.patch("/:clientId", update_client);
|
||||||
clientRoutes.delete("/:clientId", delete_client);
|
clientRoutes.delete("/:clientId", delete_client);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -568,6 +568,369 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Change bank details
|
||||||
|
async function changeBankDetails(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const formData = {
|
||||||
|
userDetails: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const activePaymentDetails = document.querySelector('.payment-details.active');
|
||||||
|
if (activePaymentDetails) {
|
||||||
|
const requiredFields = activePaymentDetails.querySelectorAll('[required]');
|
||||||
|
let isValid = true;
|
||||||
|
formData.userDetails.paymentMethod = activePaymentDetails.id === 'card-details' ? 'card' : 'bank';
|
||||||
|
|
||||||
|
// Validate card payment
|
||||||
|
if (formData.userDetails.paymentMethod === 'card') {
|
||||||
|
// Card number validation
|
||||||
|
const cardNumber = document.getElementById('card-number').value;
|
||||||
|
if (!validateCardNumber(cardNumber)) {
|
||||||
|
isValid = false;
|
||||||
|
showError('card-number');
|
||||||
|
return isValid;
|
||||||
|
} else {
|
||||||
|
// Show card type
|
||||||
|
const cardType = detectCardType(cardNumber);
|
||||||
|
if (cardType !== 'unknown') {
|
||||||
|
// You can display card type to user if needed
|
||||||
|
console.log('Detected card type:', cardType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expiry date validation
|
||||||
|
const expiryDate = document.getElementById('expiry-date').value;
|
||||||
|
if (!validateExpiryDate(expiryDate)) {
|
||||||
|
isValid = false;
|
||||||
|
showError('expiry-date');
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CVV validation
|
||||||
|
const cvv = document.getElementById('security-code').value;
|
||||||
|
const cardType = detectCardType(cardNumber);
|
||||||
|
if (!validateCVV(cvv, cardType)) {
|
||||||
|
isValid = false;
|
||||||
|
showError('security-code');
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZIP code validation
|
||||||
|
const zipCode = document.getElementById('card-zip').value;
|
||||||
|
if (!validateZipCodeRegex(zipCode)) {
|
||||||
|
isValid = false;
|
||||||
|
showError('card-zip');
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// Validate bank payment
|
||||||
|
else if (formData.userDetails.paymentMethod === 'bank') {
|
||||||
|
// Account number validation
|
||||||
|
const accountNumber = document.getElementById('account-number').value;
|
||||||
|
if (!validateBankAccount(accountNumber)) {
|
||||||
|
isValid = false;
|
||||||
|
showError('account-number');
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Routing number validation
|
||||||
|
const routingNumber = document.getElementById('routing-number').value;
|
||||||
|
if (!validateRoutingNumber(routingNumber)) {
|
||||||
|
isValid = false;
|
||||||
|
showError('routing-number');
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic validation for other bank fields
|
||||||
|
requiredFields.forEach(field => {
|
||||||
|
if (!field.value.trim()) {
|
||||||
|
isValid = false;
|
||||||
|
field.classList.add('field-error');
|
||||||
|
if (isValid === false) {
|
||||||
|
showError(field.id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
field.classList.remove('field-error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let accountCardNumber = document.getElementById('card-number');
|
||||||
|
let accountExpiryDate = document.getElementById('expiry-date');
|
||||||
|
let accountSecurityCode = document.getElementById('security-code');
|
||||||
|
let accountCardZip = document.getElementById('card-zip');
|
||||||
|
let accountBankName = document.getElementById('bank-name');
|
||||||
|
let accountAccountNumber = document.getElementById('account-number');
|
||||||
|
let accountRoutingNumber = document.getElementById('routing-number');
|
||||||
|
let accountAccountType = document.getElementById('account-type');
|
||||||
|
let accountAccountHolderName = document.getElementById('account-holder-name');
|
||||||
|
|
||||||
|
if(accountCardNumber.value) {
|
||||||
|
formData.userDetails.cardNumber = accountCardNumber.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(accountExpiryDate.value) {
|
||||||
|
formData.userDetails.expiryDate = accountExpiryDate.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(accountSecurityCode.value) {
|
||||||
|
formData.userDetails.securityCode = accountSecurityCode.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(accountCardZip.value) {
|
||||||
|
formData.userDetails.cardZip = accountCardZip.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(accountBankName.value) {
|
||||||
|
formData.userDetails.bankName = accountBankName.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(accountAccountNumber.value) {
|
||||||
|
formData.userDetails.accountNumber = accountAccountNumber.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(accountRoutingNumber.value) {
|
||||||
|
formData.userDetails.routingNumber = accountRoutingNumber.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(accountAccountType.value) {
|
||||||
|
formData.userDetails.accountType = accountAccountType.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(accountAccountHolderName.value) {
|
||||||
|
formData.userDetails.accountHolderName = accountAccountHolderName.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const saveBtn = document.getElementById('saveBankDetailsBtn');
|
||||||
|
saveBtn.disabled = true;
|
||||||
|
saveBtn.textContent = 'Updatind Details...';
|
||||||
|
|
||||||
|
const response = await fetchWithAuth(`${API_BASE}/profile`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(formData)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response) {
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
saveBtn.textContent = 'Update Bank Details';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
alert('Bank details updated successfully!');
|
||||||
|
closeBankDetailsModal();
|
||||||
|
} else {
|
||||||
|
alert(result.message || 'Failed to update bank details');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error changing Bank Details:', error);
|
||||||
|
alert('Error changing bank details');
|
||||||
|
} finally {
|
||||||
|
const saveBtn = document.getElementById('saveBankDetailsBtn');
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
saveBtn.textContent = 'Update Bank Details';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bank account validation
|
||||||
|
function validateBankAccount(accountNumber) {
|
||||||
|
return /^\d{8,17}$/.test(accountNumber.replace(/\s/g, ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Routing number validation (basic US routing number check)
|
||||||
|
function validateRoutingNumber(routingNumber) {
|
||||||
|
const cleanRouting = routingNumber.replace(/\s/g, '');
|
||||||
|
|
||||||
|
if (!/^\d{9}$/.test(cleanRouting)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for invalid patterns
|
||||||
|
if (isInvalidRoutingNumberPattern(cleanRouting)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ABA routing number checksum validation
|
||||||
|
return isValidABAChecksum(cleanRouting);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInvalidRoutingNumberPattern(routingNumber) {
|
||||||
|
const invalidPatterns = [
|
||||||
|
/^0+$/, // All zeros
|
||||||
|
/^1+$/, // All ones
|
||||||
|
/^2+$/, // All twos
|
||||||
|
/^3+$/, // All threes
|
||||||
|
/^4+$/, // All fours
|
||||||
|
/^5+$/, // All fives
|
||||||
|
/^6+$/, // All sixes
|
||||||
|
/^7+$/, // All sevens
|
||||||
|
/^8+$/, // All eights
|
||||||
|
/^9+$/, // All nines
|
||||||
|
/^012345678/, // Sequential
|
||||||
|
/^876543210/, // Reverse sequential
|
||||||
|
];
|
||||||
|
|
||||||
|
return invalidPatterns.some(pattern => pattern.test(routingNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidABAChecksum(routingNumber) {
|
||||||
|
const digits = routingNumber.split('').map(Number);
|
||||||
|
|
||||||
|
// ABA routing number checksum formula
|
||||||
|
const sum = (3 * (digits[0] + digits[3] + digits[6])) +
|
||||||
|
(7 * (digits[1] + digits[4] + digits[7])) +
|
||||||
|
(1 * (digits[2] + digits[5] + digits[8]));
|
||||||
|
|
||||||
|
return sum % 10 === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Expiry date validation
|
||||||
|
function validateExpiryDate(expiryDate) {
|
||||||
|
const expiryRegex = /^(0[1-9]|1[0-2])\/([0-9]{2})$/;
|
||||||
|
if (!expiryRegex.test(expiryDate)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [month, year] = expiryDate.split('/');
|
||||||
|
const expiry = new Date(2000 + parseInt(year), parseInt(month));
|
||||||
|
const currentDate = new Date();
|
||||||
|
const currentMonth = new Date(currentDate.getFullYear(), currentDate.getMonth());
|
||||||
|
|
||||||
|
return expiry >= currentMonth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CVV validation
|
||||||
|
function validateCVV(cvv, cardType) {
|
||||||
|
if (!/^\d+$/.test(cvv)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cvvLengths = {
|
||||||
|
visa: 3,
|
||||||
|
mastercard: 3,
|
||||||
|
amex: 4,
|
||||||
|
discover: 3,
|
||||||
|
diners: 3,
|
||||||
|
jcb: 3
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedLength = cvvLengths[cardType] || 3;
|
||||||
|
return cvv.length === expectedLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZIP code validation
|
||||||
|
function validateZipCodeRegex(zipCode) {
|
||||||
|
return /^\d{5}(-\d{4})?$/.test(zipCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateCardNumber(cardNumber) {
|
||||||
|
const cleanNumber = cardNumber.replace(/[\s-]/g, '');
|
||||||
|
|
||||||
|
// Check if it's all digits and has proper length
|
||||||
|
if (!/^\d+$/.test(cleanNumber) || cleanNumber.length < 13 || cleanNumber.length > 19) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for invalid patterns
|
||||||
|
if (isInvalidCardPattern(cleanNumber)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for invalid BIN ranges
|
||||||
|
if (hasInvalidBIN(cleanNumber)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Luhn algorithm
|
||||||
|
return isValidLuhn(cleanNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced Luhn algorithm
|
||||||
|
function isValidLuhn(cardNumber) {
|
||||||
|
let sum = 0;
|
||||||
|
let isEven = false;
|
||||||
|
|
||||||
|
for (let i = cardNumber.length - 1; i >= 0; i--) {
|
||||||
|
let digit = parseInt(cardNumber.charAt(i));
|
||||||
|
|
||||||
|
if (isEven) {
|
||||||
|
digit *= 2;
|
||||||
|
if (digit > 9) {
|
||||||
|
digit -= 9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sum += digit;
|
||||||
|
isEven = !isEven;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum % 10 === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to check for invalid card patterns
|
||||||
|
function isInvalidCardPattern(cardNumber) {
|
||||||
|
const invalidPatterns = [
|
||||||
|
/^0+$/, // All zeros
|
||||||
|
/^1+$/, // All ones
|
||||||
|
/^2+$/, // All twos
|
||||||
|
/^3+$/, // All threes
|
||||||
|
/^4+$/, // All fours
|
||||||
|
/^5+$/, // All fives
|
||||||
|
/^6+$/, // All sixes
|
||||||
|
/^7+$/, // All sevens
|
||||||
|
/^8+$/, // All eights
|
||||||
|
/^9+$/, // All nines
|
||||||
|
/^0123/, // Sequential
|
||||||
|
/^1234/, // Sequential
|
||||||
|
];
|
||||||
|
|
||||||
|
return invalidPatterns.some(pattern => pattern.test(cardNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to check for invalid BIN ranges
|
||||||
|
function hasInvalidBIN(cardNumber) {
|
||||||
|
const firstSix = cardNumber.substring(0, 6);
|
||||||
|
const firstFour = cardNumber.substring(0, 4);
|
||||||
|
const firstTwo = cardNumber.substring(0, 2);
|
||||||
|
|
||||||
|
// Invalid BIN ranges
|
||||||
|
const invalidBINs = [
|
||||||
|
'000000', '000001', '000002', '000003', // Test cards should be handled separately
|
||||||
|
'111111', '222222', '333333', '444444',
|
||||||
|
'555555', '666666', '777777', '888888',
|
||||||
|
'999999', '123456', '654321'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check specific invalid ranges
|
||||||
|
if (invalidBINs.includes(firstSix)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for known invalid starting digits
|
||||||
|
if (['0', '3', '6', '7', '8'].includes(firstTwo)) {
|
||||||
|
// 0 - Reserved
|
||||||
|
// 3 - Should only be 34, 37 (AMEX)
|
||||||
|
// 6 - Should follow Discover patterns
|
||||||
|
// 7, 8 - Not currently assigned to major networks
|
||||||
|
if (!isValidCardPrefix(cardNumber)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidCardPrefix(cardNumber) {
|
||||||
|
const type = detectCardType(cardNumber);
|
||||||
|
return type !== 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
// Logout Modal Functions
|
// Logout Modal Functions
|
||||||
function openLogoutModal() {
|
function openLogoutModal() {
|
||||||
const modal = document.getElementById('logoutModal');
|
const modal = document.getElementById('logoutModal');
|
||||||
|
|
@ -592,6 +955,7 @@
|
||||||
|
|
||||||
// Password form submission
|
// Password form submission
|
||||||
document.getElementById('passwordForm').addEventListener('submit', changePassword);
|
document.getElementById('passwordForm').addEventListener('submit', changePassword);
|
||||||
|
document.getElementById('payment-form').addEventListener('submit', changeBankDetails);
|
||||||
|
|
||||||
// Close modal when clicking outside
|
// Close modal when clicking outside
|
||||||
document.getElementById('passwordModal').addEventListener('click', function(e) {
|
document.getElementById('passwordModal').addEventListener('click', function(e) {
|
||||||
|
|
@ -1253,6 +1617,20 @@
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function showError(fieldId = null) {
|
||||||
|
// Highlight specific field if provided
|
||||||
|
if (fieldId) {
|
||||||
|
const field = document.getElementById(fieldId);
|
||||||
|
if (field) {
|
||||||
|
field.classList.add('field-error');
|
||||||
|
// Remove error after 5 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
field.classList.remove('field-error');
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Loading…
Reference in New Issue