Compare commits

...

1 Commits

Author SHA1 Message Date
Aakash Mittal cf97a7a65b Added update bank details option in client dashboard 2025-12-18 19:56:48 +05:30
4 changed files with 470 additions and 0 deletions

View File

@ -1977,4 +1977,14 @@ tr[data-vaid]:hover {
font-size: 12px; font-size: 12px;
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;
} }

View File

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

View File

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

View File

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