va-tracker-middleware/public/js/va-client-register.js

2225 lines
81 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Email validation function
function validateEmail(email) {
const emailRegex = /^[a-zA-Z0-9](?:[a-zA-Z0-9]*[._-]?[a-zA-Z0-9]+)*@[a-zA-Z0-9](?:[a-zA-Z0-9]*[-]?[a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$/;
return emailRegex.test(email);
}
// Card type detection
function detectCardType(cardNumber) {
// Remove spaces and dashes
const cleanNumber = cardNumber.replace(/[\s-]/g, '');
const cardPatterns = {
visa: /^4[0-9]{12}(?:[0-9]{3})?$/,
mastercard: /^5[1-5][0-9]{14}$/,
amex: /^3[47][0-9]{13}$/,
discover: /^6(?:011|5[0-9]{2})[0-9]{12}$/,
diners: /^3(?:0[0-5]|[68][0-9])[0-9]{11}$/,
jcb: /^(?:2131|1800|35\d{3})\d{11}$/
};
for (const [type, pattern] of Object.entries(cardPatterns)) {
if (pattern.test(cleanNumber)) {
return type;
}
}
return 'unknown';
}
// Card number validation (Luhn algorithm)
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;
}
// Validate card prefix
function isValidCardPrefix(cardNumber) {
const type = detectCardType(cardNumber);
return type !== 'unknown';
}
// 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);
}
// 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;
}
const formData = {
userDetails: {}
};
// Current step tracking
let currentStep = 1;
const statesByCountry = {
'US': [
'Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', 'Delaware',
'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky',
'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi',
'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico',
'New York', 'North Carolina', 'North Dakota', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania',
'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont',
'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming'
],
'CA': [
'Alberta', 'British Columbia', 'Manitoba', 'New Brunswick', 'Newfoundland and Labrador',
'Nova Scotia', 'Ontario', 'Prince Edward Island', 'Quebec', 'Saskatchewan'
],
'UK': [
'England', 'Scotland', 'Wales', 'Northern Ireland'
],
'AU': [
'New South Wales', 'Queensland', 'South Australia', 'Tasmania', 'Victoria', 'Western Australia',
'Australian Capital Territory', 'Northern Territory'
],
'IN': [
'Andhra Pradesh', 'Arunachal Pradesh', 'Assam', 'Bihar', 'Chhattisgarh', 'Goa', 'Gujarat',
'Haryana', 'Himachal Pradesh', 'Jharkhand', 'Karnataka', 'Kerala', 'Madhya Pradesh',
'Maharashtra', 'Manipur', 'Meghalaya', 'Mizoram', 'Nagaland', 'Odisha', 'Punjab', 'Rajasthan',
'Sikkim', 'Tamil Nadu', 'Telangana', 'Tripura', 'Uttar Pradesh', 'Uttarakhand', 'West Bengal',
'Andaman and Nicobar Islands', 'Chandigarh', 'Dadra and Nagar Haveli and Daman and Diu',
'Delhi', 'Jammu and Kashmir', 'Ladakh', 'Lakshadweep', 'Puducherry'
],
'DE': [
'Baden-Württemberg', 'Bavaria', 'Berlin', 'Brandenburg', 'Bremen', 'Hamburg', 'Hesse',
'Lower Saxony', 'Mecklenburg-Vorpommern', 'North Rhine-Westphalia', 'Rhineland-Palatinate',
'Saarland', 'Saxony', 'Saxony-Anhalt', 'Schleswig-Holstein', 'Thuringia'
],
'FR': [
'Auvergne-Rhône-Alpes', 'Bourgogne-Franche-Comté', 'Brittany', 'Centre-Val de Loire', 'Corsica',
'Grand Est', 'Hauts-de-France', 'Île-de-France', 'Normandy', 'Nouvelle-Aquitaine', 'Occitanie',
'Pays de la Loire', 'Provence-Alpes-Côte d’Azur'
],
'IT': [
'Abruzzo', 'Basilicata', 'Calabria', 'Campania', 'Emilia-Romagna', 'Friuli Venezia Giulia',
'Lazio', 'Liguria', 'Lombardy', 'Marche', 'Molise', 'Piedmont', 'Sardinia', 'Sicily',
'Trentino-Alto Adige', 'Tuscany', 'Umbria', 'Aosta Valley', 'Veneto'
],
'ES': [
'Andalusia', 'Aragon', 'Asturias', 'Balearic Islands', 'Basque Country', 'Canary Islands',
'Cantabria', 'Castile and León', 'Castile-La Mancha', 'Catalonia', 'Extremadura', 'Galicia',
'La Rioja', 'Madrid', 'Murcia', 'Navarre', 'Valencia'
],
'BR': [
'Acre', 'Alagoas', 'Amapá', 'Amazonas', 'Bahia', 'Ceará', 'Distrito Federal', 'Espírito Santo',
'Goiás', 'Maranhão', 'Mato Grosso', 'Mato Grosso do Sul', 'Minas Gerais', 'Pará', 'Paraíba',
'Paraná', 'Pernambuco', 'Piauí', 'Rio de Janeiro', 'Rio Grande do Norte', 'Rio Grande do Sul',
'Rondônia', 'Roraima', 'Santa Catarina', 'São Paulo', 'Sergipe', 'Tocantins'
],
'MX': [
'Aguascalientes', 'Baja California', 'Baja California Sur', 'Campeche', 'Chiapas', 'Chihuahua',
'Coahuila', 'Colima', 'Durango', 'Guanajuato', 'Guerrero', 'Hidalgo', 'Jalisco', 'Mexico State',
'Michoacán', 'Morelos', 'Nayarit', 'Nuevo León', 'Oaxaca', 'Puebla', 'Querétaro', 'Quintana Roo',
'San Luis Potosí', 'Sinaloa', 'Sonora', 'Tabasco', 'Tamaulipas', 'Tlaxcala', 'Veracruz',
'Yucatán', 'Zacatecas'
],
'CN': [
'Anhui', 'Beijing', 'Chongqing', 'Fujian', 'Gansu', 'Guangdong', 'Guangxi', 'Guizhou', 'Hainan',
'Hebei', 'Heilongjiang', 'Henan', 'Hubei', 'Hunan', 'Inner Mongolia', 'Jiangsu', 'Jiangxi',
'Jilin', 'Liaoning', 'Ningxia', 'Qinghai', 'Shaanxi', 'Shandong', 'Shanghai', 'Shanxi',
'Sichuan', 'Tianjin', 'Tibet', 'Xinjiang', 'Yunnan', 'Zhejiang'
],
'JP': [
'Aichi', 'Akita', 'Aomori', 'Chiba', 'Ehime', 'Fukui', 'Fukuoka', 'Fukushima', 'Gifu', 'Gunma',
'Hiroshima', 'Hokkaido', 'Hyogo', 'Ibaraki', 'Ishikawa', 'Iwate', 'Kagawa', 'Kagoshima', 'Kanagawa',
'Kochi', 'Kumamoto', 'Kyoto', 'Mie', 'Miyagi', 'Miyazaki', 'Nagano', 'Nagasaki', 'Nara', 'Niigata',
'Oita', 'Okayama', 'Okinawa', 'Osaka', 'Saga', 'Saitama', 'Shiga', 'Shimane', 'Shizuoka', 'Tochigi',
'Tokushima', 'Tokyo', 'Tottori', 'Toyama', 'Wakayama', 'Yamagata', 'Yamaguchi', 'Yamanashi'
]
};
// Function to navigate to any step (for tab navigation)
function goToStep(step) {
// Only allow navigation to completed steps or the next step
if (step > currentStep && step > getMaxCompletedStep() + 1) {
return;
}
// Hide current step
document.getElementById(`step-${currentStep}`).classList.remove('active');
document.querySelectorAll(`.step[data-step="${currentStep}"]`).forEach(el => {
el.classList.remove('active');
});
// Show target step
document.getElementById(`step-${step}`).classList.add('active');
document.querySelectorAll(`.step[data-step="${step}"]`).forEach(el => {
el.classList.add('active');
});
currentStep = step;
}
// Function to get the maximum completed step
function getMaxCompletedStep() {
const completedSteps = document.querySelectorAll('.step.completed');
let maxStep = 0;
completedSteps.forEach(step => {
const stepNum = parseInt(step.getAttribute('data-step'));
if (stepNum > maxStep) maxStep = stepNum;
});
return maxStep;
}
async function handleNextStep(step) {
await nextStep(step);
}
// Function to navigate to next step
async function nextStep(step) {
// Validate current step before proceeding
if (!(await validateStep(currentStep))) {
return;
}
// Save current step data
saveStepData(currentStep);
// Mark current step as completed
document.querySelectorAll(`.step[data-step="${currentStep}"]`).forEach(el => {
el.classList.add('completed');
const stepNumber = el.querySelector('.step-number');
stepNumber.textContent = '✓';
stepNumber.style.backgroundColor = '#000000';
stepNumber.style.borderColor = '#000000';
stepNumber.style.color = 'white';
});
// Hide current step
document.getElementById(`step-${currentStep}`).classList.remove('active');
document.querySelectorAll(`.step[data-step="${currentStep}"]`).forEach(el => {
el.classList.remove('active');
});
// Show next step
document.getElementById(`step-${step}`).classList.add('active');
document.querySelectorAll(`.step[data-step="${step}"]`).forEach(el => {
el.classList.add('active');
});
currentStep = step;
}
// Function to navigate to previous step
function prevStep(step) {
// Hide current step
document.getElementById(`step-${currentStep}`).classList.remove('active');
document.querySelectorAll(`.step[data-step="${currentStep}"]`).forEach(el => {
el.classList.remove('active');
});
// Show previous step
document.getElementById(`step-${step}`).classList.add('active');
document.querySelectorAll(`.step[data-step="${step}"]`).forEach(el => {
el.classList.add('active');
});
currentStep = step;
}
// Function to validate current step
async function validateStep(step) {
let isValid = true;
const form = document.getElementById(getFormId(step));
if (form) {
if (step === 5) {
const selectedPayment = document.querySelector('.payment-option.selected');
if (!selectedPayment) {
isValid = false;
showError('Please select a payment method', null, step);
return isValid;
}
const activePaymentDetails = document.querySelector('.payment-details.active');
if (activePaymentDetails) {
const requiredFields = activePaymentDetails.querySelectorAll('[required]');
// Validate card payment
if (formData.userDetails.paymentMethod === 'card') {
// Card number validation
const cardNumber = document.getElementById('card-number').value;
if (!validateCardNumber(cardNumber)) {
isValid = false;
showError('Please enter a valid card number', 'card-number', step);
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('Please enter a valid expiry date (MM/YY)', 'expiry-date', step);
return isValid;
}
// CVV validation
const cvv = document.getElementById('security-code').value;
const cardType = detectCardType(cardNumber);
if (!validateCVV(cvv, cardType)) {
isValid = false;
showError(`Please enter a valid ${cardType === 'amex' ? '4-digit' : '3-digit'} CVV`, 'security-code', step);
return isValid;
}
// ZIP code validation
const zipCode = document.getElementById('card-zip').value;
if (!validateZipCodeRegex(zipCode)) {
isValid = false;
showError('Please enter a valid ZIP code', 'card-zip', step);
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('Please enter a valid account number (8-17 digits)', 'account-number', step);
return isValid;
}
// Routing number validation
const routingNumber = document.getElementById('routing-number').value;
if (!validateRoutingNumber(routingNumber)) {
isValid = false;
showError('Please enter a valid routing numbers', 'routing-number', step);
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(`Please fill in ${field.labels[0]?.textContent || 'this field'}`, field.id, step);
}
} else {
field.classList.remove('field-error');
}
});
}
}
} else if (step === 1) {
const email = document.getElementById('email').value;
const firstName = document.getElementById('first-name').value;
const lastName = document.getElementById('last-name').value;
const password = document.getElementById('password').value;
const confirmPassword = document.getElementById('confirmPassword').value;
if (!firstName && !lastName && !password && !confirmPassword) {
isValid = false;
showError('Fields are Required', null, step);
let requiredFields1 = form.querySelectorAll('[required]');
requiredFields1.forEach(field => {
if (!field.value.trim()) {
isValid = false;
field.classList.add('field-error');
} else if (field.id !== 'email') {
field.classList.remove('field-error');
}
});
return isValid;
}
if (!validateEmail(email)) {
isValid = false;
showError('Please enter a valid email address', 'email', step);
} else {
const isEmailVerified = await verifyEmail(email);
if (!isEmailVerified) {
showError('Email already registered. Please use a different email address.', 'email', step);
isValid = false;
}
}
if (!firstName) {
isValid = false;
showError('First Name is Required', 'first-name', step);
return isValid;
}
if (!lastName) {
isValid = false;
showError('Last Name is Required', 'last-name', step);
return isValid;
}
if (password.length < 8) {
showError('Password must be at least 8 characters long', 'password', step);
isValid = false;
}
if (password !== confirmPassword) {
document.getElementById('password-match-error').style.display = 'block';
document.getElementById('confirmPassword').classList.add('field-error');
showError('Passwords do not match', 'confirmPassword', step);
isValid = false;
} else {
document.getElementById('password-match-error').style.display = 'none';
document.getElementById('confirmPassword').classList.remove('field-error');
}
// Validate other required fields
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(field => {
if (field.id !== 'email' && !field.value.trim()) {
isValid = false;
field.classList.add('field-error');
} else if (field.id !== 'email') {
field.classList.remove('field-error');
}
});
} else if (step === 2) {
const companyName = document.getElementById('company-name').value;
if (!companyName) {
isValid = false;
showError('Company Name is Required', 'company-name', step);
return isValid;
}
// Validate other required fields
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(field => {
if (!field.value.trim()) {
isValid = false;
field.classList.add('field-error');
} else if (field.id !== 'email') {
field.classList.remove('field-error');
}
});
} else if (step === 3) {
const email = document.getElementById('billing-email').value;
if (!validateEmail(email)) {
isValid = false;
showError('Please enter a valid email address', 'billing-email', step);
}
// Validate other required fields
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(field => {
if (field.id !== 'email' && !field.value.trim()) {
isValid = false;
field.classList.add('field-error');
} else if (field.id !== 'email') {
field.classList.remove('field-error');
}
});
} else if (step === 4) {
const email = document.getElementById('signatory-email').value;
if (!validateEmail(email)) {
isValid = false;
showError('Please enter a valid email address', 'signatory-email', step);
}
const signatoryName = document.getElementById('signatory-name').value;
if (!signatoryName) {
isValid = false;
showError('Please enter a valid name', 'signatory-name', step);
}
if (!signatoryName && !validateEmail(email)) {
isValid = false;
showError('Name and Email are Required', 'signatory-name', step);
return isValid;
}
// Validate other required fields
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(field => {
if (field.id !== 'email' && !field.value.trim()) {
isValid = false;
field.classList.add('field-error');
} else if (field.id !== 'email') {
field.classList.remove('field-error');
}
});
} else {
// For other steps, validate all required fields normally
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(field => {
if (!field.value.trim()) {
isValid = false;
field.classList.add('field-error');
} else {
field.classList.remove('field-error');
}
});
}
}
return isValid;
}
// Function to get form ID based on step
function getFormId(step) {
switch(step) {
case 1: return 'account-form';
case 2: return 'business-form';
case 3: return 'billing-form';
case 4: return 'signatory-form';
case 5: return 'payment-form';
default: return '';
}
}
// Function to save step data to formData object
function saveStepData(step) {
switch(step) {
case 1:
formData.userDetails.firstName = document.getElementById('first-name').value;
formData.userDetails.lastName = document.getElementById('last-name').value;
formData.userDetails.email = document.getElementById('email').value;
formData.userDetails.password = document.getElementById('password').value;
break;
case 2:
formData.userDetails.name = document.getElementById('company-name').value;
formData.userDetails.address = document.getElementById('address').value;
formData.userDetails.city = document.getElementById('city').value;
formData.userDetails.state = document.getElementById('state').value;
formData.userDetails.zip = document.getElementById('zip').value;
break;
case 3:
formData.userDetails.billingName = document.getElementById('billing-name').value;
formData.userDetails.billingAddress = document.getElementById('billing-address').value;
formData.userDetails.billingCity = document.getElementById('billing-city').value;
formData.userDetails.billingState = document.getElementById('billing-state').value;
formData.userDetails.billingZip = document.getElementById('billing-zip').value;
formData.userDetails.billingEmail = document.getElementById('billing-email').value;
break;
case 4:
formData.userDetails.position = document.getElementById('position').value;
formData.userDetails.signatoryName = document.getElementById('signatory-name').value;
formData.userDetails.signatoryEmail = document.getElementById('signatory-email').value;
break;
case 5:
// Payment method already handled in selectPaymentOption
break;
}
}
// Function to select payment option
function selectPaymentOption(type) {
// Remove selected class from all options
document.querySelectorAll('.payment-option').forEach(option => {
option.classList.remove('selected');
});
document.querySelectorAll('.payment-details').forEach(details => {
details.classList.remove('active');
// Remove required attributes from all payment fields
details.querySelectorAll('[required]').forEach(field => {
field.removeAttribute('required');
});
});
// Add selected class to clicked option
event.currentTarget.classList.add('selected');
if (type === 'card') {
document.getElementById('card-details').classList.add('active');
formData.userDetails.paymentMethod = 'card';
// Add required attributes only to card fields
document.getElementById('card-number').setAttribute('required', 'true');
document.getElementById('expiry-date').setAttribute('required', 'true');
document.getElementById('security-code').setAttribute('required', 'true');
document.getElementById('card-zip').setAttribute('required', 'true');
} else if (type === 'bank') {
document.getElementById('bank-details').classList.add('active');
formData.userDetails.paymentMethod = 'bank';
// Add required attributes only to bank fields
document.getElementById('bank-name').setAttribute('required', 'true');
document.getElementById('account-number').setAttribute('required', 'true');
document.getElementById('routing-number').setAttribute('required', 'true');
document.getElementById('account-type').setAttribute('required', 'true');
document.getElementById('account-holder-name').setAttribute('required', 'true');
}
}
// Function to submit the form
async function submitForm() {
// Validate current step
if (!(await validateStep(currentStep))) {
return;
}
// Save current step data
saveStepData(currentStep);
// Save payment details based on selected method
let cardNumber = document.getElementById('card-number');
let expiryDate = document.getElementById('expiry-date');
let securityCode = document.getElementById('security-code');
let cardZip = document.getElementById('card-zip');
let bankName = document.getElementById('bank-name');
let accountNumber = document.getElementById('account-number');
let routingNumber = document.getElementById('routing-number');
let accountType = document.getElementById('account-type');
let accountHolderName = document.getElementById('account-holder-name');
if(cardNumber.value) {
formData.userDetails.cardNumber = cardNumber.value;
}
if(expiryDate.value) {
formData.userDetails.expiryDate = expiryDate.value;
}
if(securityCode.value) {
formData.userDetails.securityCode = securityCode.value;
}
if(cardZip.value) {
formData.userDetails.cardZip = cardZip.value;
}
if(bankName.value) {
formData.userDetails.bankName = bankName.value;
}
if(accountNumber.value) {
formData.userDetails.accountNumber = accountNumber.value;
}
if(routingNumber.value) {
formData.userDetails.routingNumber = routingNumber.value;
}
if(accountType.value) {
formData.userDetails.accountType = accountType.value;
}
if(accountHolderName.value) {
formData.userDetails.accountHolderName = accountHolderName.value;
}
// Show loading state
const submitBtn = document.querySelector('#payment-form .btn-primary');
const originalText = submitBtn.textContent;
submitBtn.textContent = 'Submitting...';
submitBtn.disabled = true;
try {
// console.log('Submitting form data:', formData);
// Send data to server using fetch
const response = await fetch('/api/v1/client/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData)
});
if (response.ok) {
// Success - move to success step
// Mark current step as completed
document.querySelectorAll(`.step[data-step="${currentStep}"]`).forEach(el => {
el.classList.add('completed');
const stepNumber = el.querySelector('.step-number');
stepNumber.textContent = '✓';
stepNumber.style.backgroundColor = '#000000';
stepNumber.style.borderColor = '#000000';
stepNumber.style.color = 'white';
});
nextStep(6);
setTimeout(() => {
window.location.href = '/';
}, 1000);
} else {
// Handle server errors
if (response.status === 409) {
showError('A user with this email already exists. Please use a different email.', null, 5);
} else {
showError('There was an error submitting your registration. Please try again.', null, 5);
}
}
} catch (error) {
// Handle network errors
console.error('Error:', error);
showError('There was a network error. Please check your connection and try again.', null, 5);
} finally {
// Restore button state
submitBtn.textContent = originalText;
submitBtn.disabled = false;
}
}
// Function to fetch states from API
async function fetchStates(countryCode) {
try {
// Replace with your actual API endpoint
const response = await fetch(`/api/states/${countryCode}`);
if (response.ok) {
const states = await response.json();
return states;
} else {
console.error('Failed to fetch states');
return [];
}
} catch (error) {
console.error('Error fetching states:', error);
return [];
}
}
// Function to populate states dropdown
async function populateStates(countryCode, selectElementId) {
const selectElement = document.getElementById(selectElementId);
const states = statesByCountry[countryCode] || [];
// Clear existing options except the first empty one
while (selectElement.options.length > 1) {
selectElement.remove(1);
}
// Add states as options
states.forEach(state => {
const option = document.createElement('option');
option.value = state;
option.textContent = state;
selectElement.appendChild(option);
});
}
// Function to detect user location
async function detectLocation() {
// Show loading indicators
const stateLoader = document.getElementById('state-loading');
if (stateLoader) stateLoader.style.display = 'block';
try {
const response = await fetch('https://ipapi.co/json/');
const data = await response.json();
// Hide loading indicators
if (stateLoader) stateLoader.style.display = 'none';
// const countryCode = data.country_code || 'US';
const supportedCountries = Object.keys(statesByCountry);
const countryCode = supportedCountries.includes(data.country_code)
? data.country_code
: 'US';
const region = data.region || '';
// const city = data.city || '';
// const zip = data.postal || '';
// Populate city and state based on detected location
// const cityInput = document.getElementById('city');
// const zipInput = document.getElementById('zip');
// if (cityInput) cityInput.value = city;
// if (zipInput) zipInput.value = zip;
// Populate states dropdown for the detected country
await populateStates(countryCode, 'state');
await populateStates(countryCode, 'billing-state');
// // auto populate state if region matches
// const stateSelect = document.getElementById('state');
// const billingStateSelect = document.getElementById('billing-state');
// if (stateSelect && region) {
// const matchedState = Array.from(stateSelect.options).find(
// opt => opt.text.toLowerCase() === region.toLowerCase()
// );
// if (matchedState) {
// stateSelect.value = matchedState.value;
// stateSelect.dispatchEvent(new Event('change'));
// }
// }
// if (billingStateSelect && region) {
// const matchedBillingState = Array.from(billingStateSelect.options).find(
// opt => opt.text.toLowerCase() === region.toLowerCase()
// );
// if (matchedBillingState) {
// billingStateSelect.value = matchedBillingState.value;
// billingStateSelect.dispatchEvent(new Event('change'));
// }
// }
} catch (error) {
console.error('Error detecting location:', error);
// Hide loading indicators
if (stateLoader) stateLoader.style.display = 'none';
// Fallback to US states if detection fails
await populateStates('US', 'state');
await populateStates('US', 'billing-state');
}
}
// Initialize form interactions
document.addEventListener('DOMContentLoaded', function() {
// Real-time card number formatting and validation
const cardNumberInput = document.getElementById('card-number');
if (cardNumberInput) {
cardNumberInput.addEventListener('input', function(e) {
const cursorPosition = e.target.selectionStart;
const originalLength = e.target.value.length;
// Format card number with spaces
let value = e.target.value.replace(/\s/g, '');
let formattedValue = '';
for (let i = 0; i < value.length; i++) {
if (i > 0 && i % 4 === 0) {
formattedValue += ' ';
}
formattedValue += value[i];
}
e.target.value = formattedValue;
let newCursorPosition = cursorPosition;
if (formattedValue.length > originalLength) {
const addedChars = formattedValue.length - originalLength;
// Count how many spaces were added before the cursor position
let spacesAddedBeforeCursor = 0;
for (let i = 0; i < cursorPosition && i < formattedValue.length; i++) {
if (formattedValue[i] === ' ' && (i < cursorPosition ||
(i === cursorPosition && formattedValue[i] === ' '))) {
spacesAddedBeforeCursor++;
}
}
newCursorPosition = cursorPosition + spacesAddedBeforeCursor;
} else if (formattedValue.length < originalLength) {
const removedChars = originalLength - formattedValue.length;
// Count how many spaces were removed before the cursor position
let spacesRemovedBeforeCursor = 0;
const oldValue = e.target.getAttribute('data-old-value') || '';
for (let i = 0; i < cursorPosition && i < oldValue.length; i++) {
if (oldValue[i] === ' ' && i < cursorPosition) {
spacesRemovedBeforeCursor++;
}
}
newCursorPosition = Math.max(0, cursorPosition - spacesRemovedBeforeCursor);
}
e.target.setAttribute('data-old-value', formattedValue);
e.target.setSelectionRange(newCursorPosition, newCursorPosition);
// Real-time card type detection
const cardType = detectCardType(value);
if (cardType !== 'unknown') {
// You can add visual feedback for card type here
console.log('Card type:', cardType);
}
});
cardNumberInput.addEventListener('keydown', function(e) {
// Allow: backspace, delete, tab, escape, enter, arrows, home, end
if ([8, 9, 13, 27, 37, 38, 39, 40, 46].includes(e.keyCode) ||
// Allow: Ctrl+A, Ctrl+C, Ctrl+V, Ctrl+X
(e.keyCode === 65 && e.ctrlKey === true) ||
(e.keyCode === 67 && e.ctrlKey === true) ||
(e.keyCode === 86 && e.ctrlKey === true) ||
(e.keyCode === 88 && e.ctrlKey === true) ||
// Allow: numbers and numpad numbers
(e.keyCode >= 48 && e.keyCode <= 57) ||
(e.keyCode >= 96 && e.keyCode <= 105)) {
return;
}
// Prevent all other keys
e.preventDefault();
});
}
// Real-time expiry date formatting
const expiryInput = document.getElementById('expiry-date');
if (expiryInput) {
expiryInput.addEventListener('input', function(e) {
let value = e.target.value.replace(/\D/g, '');
if (value.length >= 2) {
value = value.substring(0, 2) + '/' + value.substring(2, 4);
}
e.target.value = value;
});
}
// Real-time CVV validation
const cvvInput = document.getElementById('security-code');
if (cvvInput) {
cvvInput.addEventListener('input', function(e) {
e.target.value = e.target.value.replace(/\D/g, '').substring(0, 4);
});
}
// Real-time email validation
const emailInputs = document.querySelectorAll('input[type="email"]');
emailInputs.forEach(emailInput => {
if (emailInput) {
emailInput.addEventListener('blur', function(e) {
if (e.target.value && !validateEmail(e.target.value)) {
e.target.style.border = '1px solid #ef4444';
} else {
e.target.style.border = '';
}
});
}
});
// Add input event listeners to handle label animation
document.querySelectorAll('.input-field').forEach(input => {
input.addEventListener('input', function() {
if (this.value) {
this.nextElementSibling.style.top = '4px';
this.nextElementSibling.style.fontSize = '12px';
this.nextElementSibling.style.transform = 'none';
} else {
this.nextElementSibling.style.top = '50%';
this.nextElementSibling.style.fontSize = '14px';
this.nextElementSibling.style.transform = 'translateY(-50%)';
}
});
input.addEventListener('focus', function() {
this.nextElementSibling.style.top = '4px';
this.nextElementSibling.style.fontSize = '12px';
this.nextElementSibling.style.transform = 'none';
});
input.addEventListener('blur', function() {
if (!this.value) {
this.nextElementSibling.style.top = '50%';
this.nextElementSibling.style.fontSize = '14px';
this.nextElementSibling.style.transform = 'translateY(-50%)';
}
});
});
// Handle select field label animation
document.querySelectorAll('.select-field').forEach(select => {
select.addEventListener('change', function() {
if (this.value) {
this.nextElementSibling.style.top = '4px';
this.nextElementSibling.style.fontSize = '12px';
this.nextElementSibling.style.transform = 'none';
} else {
this.nextElementSibling.style.top = '50%';
this.nextElementSibling.style.fontSize = '14px';
this.nextElementSibling.style.transform = 'translateY(-50%)';
}
});
select.addEventListener('focus', function() {
this.nextElementSibling.style.top = '4px';
this.nextElementSibling.style.fontSize = '12px';
this.nextElementSibling.style.transform = 'none';
});
select.addEventListener('blur', function() {
if (!this.value) {
this.nextElementSibling.style.top = '50%';
this.nextElementSibling.style.fontSize = '14px';
this.nextElementSibling.style.transform = 'translateY(-50%)';
}
});
});
// Handle use business address checkbox
document.getElementById('use-business-address').addEventListener('change', function() {
if (this.checked) {
document.getElementById('billing-name').value = formData.userDetails.name || '';
document.getElementById('billing-address').value = formData.userDetails.address || '';
document.getElementById('billing-city').value = formData.userDetails.city || '';
document.getElementById('billing-state').value = formData.userDetails.state || '';
document.getElementById('billing-zip').value = formData.userDetails.zip || '';
// Trigger input events to update labels
document.querySelectorAll('#billing-form .input-field').forEach(input => {
input.dispatchEvent(new Event('input'));
});
// Trigger change event for select
document.getElementById('billing-state').dispatchEvent(new Event('change'));
} else {
document.getElementById('billing-name').value = '';
document.getElementById('billing-address').value = '';
document.getElementById('billing-city').value = '';
document.getElementById('billing-state').value = '';
document.getElementById('billing-zip').value = '';
// Trigger input events to update labels
document.querySelectorAll('#billing-form .input-field').forEach(input => {
input.dispatchEvent(new Event('input'));
});
// Trigger change event for select
document.getElementById('billing-state').dispatchEvent(new Event('change'));
}
});
// Handle use account email checkbox
document.getElementById('use-account-email').addEventListener('change', function() {
if (this.checked) {
document.getElementById('billing-email').value = formData.userDetails.email || '';
document.getElementById('billing-email').dispatchEvent(new Event('input'));
} else {
document.getElementById('billing-email').value = '';
document.getElementById('billing-email').dispatchEvent(new Event('input'));
}
});
// Detect user location when the page loads
detectLocation();
});
async function verifyEmail(email) {
try {
const response = await fetch('/api/v1/client/verify-client-email', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({"email": email})
});
if (response.ok) {
return false;
} else {
return true;
}
} catch (error) {
// Handle network errors
console.error('Error:', error);
showError('There was a network error. Please check your connection and try again.', null, 1);
return false;
}
}
function showError(message, fieldId = null, step = null) {
// Determine the current step if not provided
const currentStep = step || getCurrentStep();
const errorContainer = document.getElementById(`step-${currentStep}-error`);
const errorMessage = document.getElementById(`step-${currentStep}-error-message`);
if (errorContainer && errorMessage) {
errorMessage.textContent = message;
errorContainer.style.display = 'block';
// Scroll to error container
errorContainer.scrollIntoView({ behavior: 'smooth', block: 'center' });
// 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);
}
}
// Auto-hide error after 8 seconds
setTimeout(() => {
hideError(currentStep);
}, 8000);
}
}
function hideError(step = null) {
if (step) {
// Hide specific step error
const errorContainer = document.getElementById(`step-${step}-error`);
if (errorContainer) {
errorContainer.style.display = 'none';
}
} else {
// Hide all step errors
for (let i = 1; i <= 5; i++) {
const errorContainer = document.getElementById(`step-${i}-error`);
if (errorContainer) {
errorContainer.style.display = 'none';
}
}
}
}
function clearFieldErrors() {
document.querySelectorAll('.field-error').forEach(field => {
field.classList.remove('field-error');
});
}
function getCurrentStep() {
return currentStep;
}
// Function to validate signatory name (no numbers allowed)
function validateSignatoryName(input) {
const errorElement = document.getElementById('signatory-name-error');
const value = input.value;
// Remove any numbers from the input
const cleanValue = value.replace(/[0-9]/g, '');
// If numbers were removed, update the input value
if (value !== cleanValue) {
input.value = cleanValue;
}
// Show error if original value contained numbers
if (value !== cleanValue && value.length > 0) {
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else {
errorElement.style.display = 'none';
input.classList.remove('field-error');
return true;
}
}
// Function to prevent number input in real-time
function preventNumbers(event) {
const charCode = event.which ? event.which : event.keyCode;
// Allow letters, spaces, hyphens, apostrophes, and backspace
if ((charCode >= 65 && charCode <= 90) || // A-Z
(charCode >= 97 && charCode <= 122) || // a-z
charCode === 32 || // space
charCode === 45 || // hyphen
charCode === 39 || // apostrophe
charCode === 8 || // backspace
charCode === 46) { // delete
return true;
}
// Prevent numbers and other characters
event.preventDefault();
return false;
}
// Function to validate first name
function validateFirstName(input) {
const errorElement = document.getElementById('first-name-error');
const value = input.value;
// Check various invalid patterns
const validationResult = validateFirstNameContent(value);
if (!validationResult.isValid && value.trim().length > 0) {
errorElement.textContent = validationResult.message;
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else {
errorElement.style.display = 'none';
input.classList.remove('field-error');
return true;
}
}
function validateFirstNameContent(firstName) {
const trimmed = firstName.trim();
if (trimmed.length === 0) {
return { isValid: false, message: 'First name is required' };
}
// Check minimum length
if (trimmed.length < 2) {
return { isValid: false, message: 'First name must be at least 2 characters' };
}
// Remove whitespace for content analysis
const content = trimmed.replace(/\s/g, '');
// Check if contains only special characters
if (/^[^a-zA-Z0-9]+$/.test(content)) {
return { isValid: false, message: 'First name cannot be only special characters' };
}
// Check if contains at least some valid content (letters or numbers)
if (!/[a-zA-Z0-9]/.test(content)) {
return { isValid: false, message: 'First name must contain letters or numbers' };
}
// Optional: Check for excessive special characters (more than 50%)
const specialCharCount = (content.match(/[^a-zA-Z0-9]/g) || []).length;
if (specialCharCount > content.length / 2) {
return { isValid: false, message: 'First name contains too many special characters' };
}
return { isValid: true, message: '' };
}
// Function to prevent invalid character input in real-time
function preventInvalidFirstNameChars(event) {
const charCode = event.which ? event.which : event.keyCode;
const key = event.key;
// Allow: letters, numbers, spaces, hyphens, apostrophes, ampersands, periods, commas
// Also allow common editing keys
if ((charCode >= 65 && charCode <= 90) || // A-Z
(charCode >= 97 && charCode <= 122) || // a-z
(charCode >= 48 && charCode <= 57) || // 0-9
charCode === 32 || // space
charCode === 45 || // hyphen
charCode === 39 || // apostrophe
charCode === 38 || // ampersand (&)
charCode === 46 || // period (.)
charCode === 44 || // comma (,)
charCode === 8 || // backspace
charCode === 9 || // tab
charCode === 37 || // left arrow
charCode === 39 || // right arrow
charCode === 16 || // shift
charCode === 17 || // ctrl
charCode === 18 || // alt
charCode === 91 || // cmd (Mac)
/^[a-zA-Z0-9\s\-'&.,]$/.test(key)) { // allowed characters
return true;
}
// Prevent invalid characters
event.preventDefault();
return false;
}
// Function to validate last name
function validateLastName(input) {
const errorElement = document.getElementById('last-name-error');
const value = input.value;
// Check various invalid patterns
const validationResult = validateLastNameContent(value);
if (!validationResult.isValid && value.trim().length > 0) {
errorElement.textContent = validationResult.message;
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else {
errorElement.style.display = 'none';
input.classList.remove('field-error');
return true;
}
}
function validateLastNameContent(lastName) {
const trimmed = lastName.trim();
if (trimmed.length === 0) {
return { isValid: false, message: 'Last name is required' };
}
// Check minimum length
if (trimmed.length < 2) {
return { isValid: false, message: 'Last name must be at least 2 characters' };
}
// Remove whitespace for content analysis
const content = trimmed.replace(/\s/g, '');
// Check if contains only special characters
if (/^[^a-zA-Z0-9]+$/.test(content)) {
return { isValid: false, message: 'Last name cannot be only special characters' };
}
// Check if contains at least some valid content (letters or numbers)
if (!/[a-zA-Z0-9]/.test(content)) {
return { isValid: false, message: 'Last name must contain letters or numbers' };
}
// Optional: Check for excessive special characters (more than 50%)
const specialCharCount = (content.match(/[^a-zA-Z0-9]/g) || []).length;
if (specialCharCount > content.length / 2) {
return { isValid: false, message: 'Last name contains too many special characters' };
}
return { isValid: true, message: '' };
}
// Function to prevent invalid character input in real-time
function preventInvalidLastNameChars(event) {
const charCode = event.which ? event.which : event.keyCode;
const key = event.key;
// Allow: letters, numbers, spaces, hyphens, apostrophes, ampersands, periods, commas
// Also allow common editing keys
if ((charCode >= 65 && charCode <= 90) || // A-Z
(charCode >= 97 && charCode <= 122) || // a-z
(charCode >= 48 && charCode <= 57) || // 0-9
charCode === 32 || // space
charCode === 45 || // hyphen
charCode === 39 || // apostrophe
charCode === 38 || // ampersand (&)
charCode === 46 || // period (.)
charCode === 44 || // comma (,)
charCode === 8 || // backspace
charCode === 9 || // tab
charCode === 37 || // left arrow
charCode === 39 || // right arrow
charCode === 16 || // shift
charCode === 17 || // ctrl
charCode === 18 || // alt
charCode === 91 || // cmd (Mac)
/^[a-zA-Z0-9\s\-'&.,]$/.test(key)) { // allowed characters
return true;
}
// Prevent invalid characters
event.preventDefault();
return false;
}
// Function to validate company name
function validateCompanyName(input) {
const errorElement = document.getElementById('company-name-error');
const value = input.value;
// Check various invalid patterns
const validationResult = validateCompanyNameContent(value);
if (!validationResult.isValid && value.trim().length > 0) {
errorElement.textContent = validationResult.message;
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else {
errorElement.style.display = 'none';
input.classList.remove('field-error');
return true;
}
}
function validateCompanyNameContent(companyName) {
const trimmed = companyName.trim();
if (trimmed.length === 0) {
return { isValid: false, message: 'Company name is required' };
}
// Check minimum length
if (trimmed.length < 2) {
return { isValid: false, message: 'Company name must be at least 2 characters' };
}
// Remove whitespace for content analysis
const content = trimmed.replace(/\s/g, '');
// Check if contains only special characters
if (/^[^a-zA-Z0-9]+$/.test(content)) {
return { isValid: false, message: 'Company name cannot be only special characters' };
}
// Check if contains at least some valid content (letters or numbers)
if (!/[a-zA-Z0-9]/.test(content)) {
return { isValid: false, message: 'Company name must contain letters or numbers' };
}
// Optional: Check for excessive special characters (more than 50%)
const specialCharCount = (content.match(/[^a-zA-Z0-9]/g) || []).length;
if (specialCharCount > content.length / 2) {
return { isValid: false, message: 'Company name contains too many special characters' };
}
return { isValid: true, message: '' };
}
// Function to prevent invalid character input in real-time
function preventInvalidCompanyNameChars(event) {
const charCode = event.which ? event.which : event.keyCode;
const key = event.key;
// Allow: letters, numbers, spaces, hyphens, apostrophes, ampersands, periods, commas
// Also allow common editing keys
if ((charCode >= 65 && charCode <= 90) || // A-Z
(charCode >= 97 && charCode <= 122) || // a-z
(charCode >= 48 && charCode <= 57) || // 0-9
charCode === 32 || // space
charCode === 45 || // hyphen
charCode === 39 || // apostrophe
charCode === 38 || // ampersand (&)
charCode === 46 || // period (.)
charCode === 44 || // comma (,)
charCode === 8 || // backspace
charCode === 9 || // tab
charCode === 37 || // left arrow
charCode === 39 || // right arrow
charCode === 16 || // shift
charCode === 17 || // ctrl
charCode === 18 || // alt
charCode === 91 || // cmd (Mac)
/^[a-zA-Z0-9\s\-'&.,]$/.test(key)) { // allowed characters
return true;
}
// Prevent invalid characters
event.preventDefault();
return false;
}
// Function to validate ZIP code in real-time
function validateZipCode(input) {
let errorElement;
// Determine which error element to use based on input ID
if (input.id === 'billing-zip') {
errorElement = document.getElementById('billing-zip-error');
} else if (input.id === 'card-zip') {
errorElement = document.getElementById('card-zip-error');
} else {
errorElement = document.getElementById('zip-error');
}
const value = input.value;
// Remove any non-numeric characters except hyphens
const cleanValue = value.replace(/[^0-9\-]/g, '');
// If invalid characters were removed, update the input value
if (value !== cleanValue) {
input.value = cleanValue;
}
// Format ZIP code as user types (add hyphen after 5 digits)
if (cleanValue.length === 5 && !cleanValue.includes('-')) {
input.value = cleanValue;
} else if (cleanValue.length > 5 && !cleanValue.includes('-')) {
input.value = cleanValue.substring(0, 5) + '' + cleanValue.substring(5, 9);
}
if (input.value.length < 4) {
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else {
errorElement.style.display = 'none';
input.classList.remove('field-error');
return true;
}
}
// Function to prevent non-numeric input in ZIP fields
function preventNonNumericZip(event) {
const charCode = event.which ? event.which : event.keyCode;
const key = event.key;
const currentValue = event.target.value;
// Allow: numbers, hyphens (only in specific positions), and editing keys
if ((charCode >= 48 && charCode <= 57) || // 0-9
(charCode === 45 && currentValue.length === 5 && !currentValue.includes('-')) || // hyphen only after 5 digits
charCode === 8 || // backspace
charCode === 9 || // tab
charCode === 37 || // left arrow
charCode === 39 || // right arrow
charCode === 46 || // delete
charCode === 16 || // shift
charCode === 17 || // ctrl
charCode === 18 || // alt
charCode === 91) { // cmd (Mac)
return true;
}
// Prevent all other characters including letters and special characters
event.preventDefault();
return false;
}
// Function to validate city name in real-time
function validateCity(input) {
let errorElement;
// Determine which error element to use based on input ID
if (input.id === 'billing-city') {
errorElement = document.getElementById('billing-city-error');
} else {
errorElement = document.getElementById('city-error');
}
const value = input.value;
// Remove invalid special characters (keep only letters, spaces, hyphens, apostrophes)
const cleanValue = value.replace(/[^a-zA-Z\s\-']/g, '');
// If invalid characters were removed, update the input value
if (value !== cleanValue) {
input.value = cleanValue;
}
// Check if the value contains only numbers
const numbersOnlyCheck = value.replace(/[\s\-']/g, '');
const hasOnlyNumbers = /^[0-9]+$/.test(numbersOnlyCheck) && numbersOnlyCheck.length > 0;
// Check if the value contains only special characters
const specialCharsOnlyCheck = value.replace(/[\s]/g, '');
const hasOnlySpecialChars = /^[^a-zA-Z0-9]+$/.test(specialCharsOnlyCheck) && specialCharsOnlyCheck.length > 0;
// Check if we have at least one letter
const hasLetters = /[a-zA-Z]/.test(value);
// Show appropriate error messages
if (value !== cleanValue && value.length > 0) {
errorElement.textContent = 'City name can only contain letters, spaces, hyphens, and apostrophes';
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else if (hasOnlyNumbers) {
errorElement.textContent = 'City name cannot be only numbers. Please include letters.';
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else if (hasOnlySpecialChars) {
errorElement.textContent = 'City name cannot be only special characters. Please include letters.';
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else if (!hasLetters && value.trim().length > 0) {
errorElement.textContent = 'City name must include at least one letter';
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else {
errorElement.style.display = 'none';
input.classList.remove('field-error');
return true;
}
}
// Function to prevent invalid character input in city fields
function preventInvalidCityChars(event) {
const charCode = event.which ? event.which : event.keyCode;
const key = event.key;
// Allow: letters, spaces, hyphens, apostrophes, and editing keys
if ((charCode >= 65 && charCode <= 90) || // A-Z
(charCode >= 97 && charCode <= 122) || // a-z
charCode === 32 || // space
charCode === 45 || // hyphen
charCode === 39 || // apostrophe
charCode === 8 || // backspace
charCode === 9 || // tab
charCode === 37 || // left arrow
charCode === 39 || // right arrow
charCode === 46 || // delete
charCode === 16 || // shift
charCode === 17 || // ctrl
charCode === 18 || // alt
charCode === 91 || // cmd (Mac)
/^[a-zA-Z\s\-']$/.test(key)) { // allowed characters
return true;
}
// Prevent numbers and other special characters
event.preventDefault();
return false;
}
function validatePosition(input) {
const errorElement = document.getElementById('position-error');
const value = input.value;
// Remove numbers and invalid special characters
// Allow: letters, spaces, hyphens, apostrophes, periods, commas, ampersands, slashes
const cleanValue = value.replace(/[^a-zA-Z\s\-'.,&\/]/g, '');
// If invalid characters were removed, update the input value
if (value !== cleanValue) {
input.value = cleanValue;
}
// Check if the value contains only numbers
const numbersOnlyCheck = value.replace(/[\s\-'.,&\/]/g, '');
const hasOnlyNumbers = /^[0-9]+$/.test(numbersOnlyCheck) && numbersOnlyCheck.length > 0;
// Check if the value contains only special characters
const specialCharsOnlyCheck = value.replace(/[\s]/g, '');
const hasOnlySpecialChars = /^[^a-zA-Z0-9]+$/.test(specialCharsOnlyCheck) && specialCharsOnlyCheck.length > 0;
// Check if we have at least one letter
const hasLetters = /[a-zA-Z]/.test(value);
// Show appropriate error messages
if (value !== cleanValue && value.length > 0) {
errorElement.textContent = 'Position can only contain letters, spaces, hyphens, apostrophes, periods, commas, ampersands, and slashes';
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else if (hasOnlyNumbers) {
errorElement.textContent = 'Position cannot be only numbers. Please include letters.';
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else if (hasOnlySpecialChars) {
errorElement.textContent = 'Position cannot be only special characters. Please include letters.';
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else if (!hasLetters && value.trim().length > 0) {
errorElement.textContent = 'Position must include at least one letter';
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else {
errorElement.style.display = 'none';
input.classList.remove('field-error');
return true;
}
}
// Function to prevent invalid character input in position field
function preventInvalidPositionChars(event) {
const charCode = event.which ? event.which : event.keyCode;
const key = event.key;
// Allow: letters, spaces, hyphens, apostrophes, periods, commas, ampersands, slashes
// Also allow common editing keys
if ((charCode >= 65 && charCode <= 90) || // A-Z
(charCode >= 97 && charCode <= 122) || // a-z
charCode === 32 || // space
charCode === 45 || // hyphen
charCode === 39 || // apostrophe
charCode === 46 || // period
charCode === 44 || // comma
charCode === 38 || // ampersand (&)
charCode === 47 || // slash (/)
charCode === 8 || // backspace
charCode === 9 || // tab
charCode === 37 || // left arrow
charCode === 39 || // right arrow
charCode === 46 || // delete
charCode === 16 || // shift
charCode === 17 || // ctrl
charCode === 18 || // alt
charCode === 91 || // cmd (Mac)
/^[a-zA-Z\s\-'.,&\/]$/.test(key)) { // allowed characters
return true;
}
// Prevent numbers and other special characters
event.preventDefault();
return false;
}
// Function to validate CVV in real-time
function validateCVVVV(input) {
const errorElement = document.getElementById('security-code-error');
const value = input.value;
// Remove any non-digit characters
const cleanValue = value.replace(/\D/g, '');
// If non-digits were removed, update the input value
if (value !== cleanValue) {
input.value = cleanValue;
}
// Get card type to determine expected CVV length
const cardNumber = document.getElementById('card-number').value;
const cardType = detectCardType(cardNumber);
// Validate CVV format and content
const isValid = validateCVVStrict(cleanValue, cardType);
if (!isValid && cleanValue.length > 0) {
errorElement.textContent = getCVVErrorMessage(cleanValue, cardType);
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else {
errorElement.style.display = 'none';
input.classList.remove('field-error');
return true;
}
}
// Function to prevent invalid character input in CVV field
function preventInvalidCVVChars(event) {
const charCode = event.which ? event.which : event.keyCode;
const key = event.key;
// Allow only digits and common editing keys
if ((charCode >= 48 && charCode <= 57) || // 0-9
charCode === 8 || // backspace
charCode === 9 || // tab
charCode === 37 || // left arrow
charCode === 39 || // right arrow
charCode === 46 || // delete
charCode === 16 || // shift
charCode === 17 || // ctrl
charCode === 18 || // alt
charCode === 91) { // cmd (Mac)
return true;
}
// Prevent all non-digit characters
event.preventDefault();
return false;
}
// Enhanced CVV validation function
function validateCVVStrict(cvv, cardType) {
if (!cvv || !/^\d+$/.test(cvv)) {
return false;
}
// Check for invalid patterns
if (isInvalidCVVPattern(cvv)) {
return false;
}
const cvvLengths = {
visa: 3,
mastercard: 3,
amex: 4,
discover: 3,
diners: 3,
jcb: 3
};
const expectedLength = cvvLengths[cardType] || 3;
// Check length
if (cvv.length !== expectedLength) {
return false;
}
return true;
}
// Function to check for invalid CVV patterns
function isInvalidCVVPattern(cvv) {
const invalidPatterns = [
/^0+$/, // All zeros: 000, 0000
/^1+$/, // All ones: 111, 1111
/^2+$/, // All twos: 222, 2222
/^3+$/, // All threes: 333, 3333
/^4+$/, // All fours: 444, 4444
/^5+$/, // All fives: 555, 5555
/^6+$/, // All sixes: 666, 6666
/^7+$/, // All sevens: 777, 7777
/^8+$/, // All eights: 888, 8888
/^9+$/, // All nines: 999, 9999
];
return invalidPatterns.some(pattern => pattern.test(cvv));
}
// Function to get appropriate CVV error message
function getCVVErrorMessage(cvv, cardType) {
if (!cvv) {
return 'Security code is required';
}
if (!/^\d+$/.test(cvv)) {
return 'Security code must contain only numbers';
}
if (isInvalidCVVPattern(cvv)) {
return 'Please enter a valid security code';
}
const cvvLengths = {
visa: 3,
mastercard: 3,
amex: 4,
discover: 3,
diners: 3,
jcb: 3
};
const expectedLength = cvvLengths[cardType] || 3;
if (cvv.length !== expectedLength) {
if (cardType === 'amex') {
return 'American Express cards require a 4-digit security code';
} else {
return `Security code must be ${expectedLength} digits`;
}
}
return 'Please enter a valid security code';
}
// Bank Name validation
function validateBankName(input) {
const errorElement = document.getElementById('bank-name-error');
const value = input.value;
// Remove numbers and invalid special characters
const cleanValue = value.replace(/[^a-zA-Z\s\-'&.,()]/g, '');
if (value !== cleanValue) {
input.value = cleanValue;
}
// Check content
const contentCheck = value.replace(/[\s\-'&.,()]/g, '');
const hasOnlyNumbers = /^[0-9]+$/.test(contentCheck) && contentCheck.length > 0;
const hasOnlySpecialChars = /^[^a-zA-Z0-9]+$/.test(contentCheck) && contentCheck.length > 0;
const hasLetters = /[a-zA-Z]/.test(value);
if (value !== cleanValue && value.length > 0) {
errorElement.textContent = 'Bank name can only contain letters, spaces, and common punctuation';
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else if (hasOnlyNumbers) {
errorElement.textContent = 'Bank name cannot be only numbers';
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else if (hasOnlySpecialChars) {
errorElement.textContent = 'Bank name cannot be only special characters';
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else if (!hasLetters && value.trim().length > 0) {
errorElement.textContent = 'Bank name must include letters';
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else {
errorElement.style.display = 'none';
input.classList.remove('field-error');
return true;
}
}
function preventInvalidBankNameChars(event) {
const charCode = event.which ? event.which : event.keyCode;
const key = event.key;
if ((charCode >= 65 && charCode <= 90) ||
(charCode >= 97 && charCode <= 122) ||
charCode === 32 || charCode === 45 || charCode === 39 ||
charCode === 38 || charCode === 46 || charCode === 44 ||
charCode === 40 || charCode === 41 ||
charCode === 8 || charCode === 9 || charCode === 37 ||
charCode === 39 || charCode === 46 || charCode === 16 ||
charCode === 17 || charCode === 18 || charCode === 91 ||
/^[a-zA-Z\s\-'&.,()]$/.test(key)) {
return true;
}
event.preventDefault();
return false;
}
// Enhanced Account Number validation
function validateAccountNumber(input) {
const errorElement = document.getElementById('account-number-error');
const value = input.value.replace(/\s/g, '');
// Remove any non-digit characters
const cleanValue = value.replace(/\D/g, '');
if (value !== cleanValue) {
input.value = cleanValue;
}
// Validate account number
const isValid = validateAccountNumberStrict(cleanValue);
if (!isValid && cleanValue.length > 0) {
errorElement.textContent = getAccountNumberErrorMessage(cleanValue);
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else {
errorElement.style.display = 'none';
input.classList.remove('field-error');
return true;
}
}
function preventInvalidAccountNumberChars(event) {
const charCode = event.which ? event.which : event.keyCode;
if ((charCode >= 48 && charCode <= 57) ||
charCode === 8 || charCode === 9 || charCode === 37 ||
charCode === 39 || charCode === 46 || charCode === 16 ||
charCode === 17 || charCode === 18 || charCode === 91) {
return true;
}
event.preventDefault();
return false;
}
function validateAccountNumberStrict(accountNumber) {
if (!accountNumber || !/^\d+$/.test(accountNumber)) {
return false;
}
// Check length
if (accountNumber.length < 8 || accountNumber.length > 17) {
return false;
}
// Check for invalid patterns
if (isInvalidAccountNumberPattern(accountNumber)) {
return false;
}
// Simple checksum validation (basic example)
if (!isValidAccountNumberChecksum(accountNumber)) {
return false;
}
return true;
}
function isInvalidAccountNumberPattern(accountNumber) {
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
/^12345678/, // Sequential
/^87654321/, // Reverse sequential
];
return invalidPatterns.some(pattern => pattern.test(accountNumber));
}
function isValidAccountNumberChecksum(accountNumber) {
// Basic checksum validation - you might want to implement
// a more specific algorithm based on bank requirements
let sum = 0;
for (let i = 0; i < accountNumber.length; i++) {
sum += parseInt(accountNumber.charAt(i));
}
// Simple check: sum shouldn't be too low (e.g., all zeros)
return sum > 0;
}
function getAccountNumberErrorMessage(accountNumber) {
if (!accountNumber) {
return 'Account number is required';
}
if (!/^\d+$/.test(accountNumber)) {
return 'Account number must contain only numbers';
}
if (accountNumber.length < 8) {
return 'Account number must be at least 8 digits';
}
if (accountNumber.length > 17) {
return 'Account number cannot exceed 17 digits';
}
if (isInvalidAccountNumberPattern(accountNumber)) {
return 'Please enter a valid account number';
}
return 'Please enter a valid account number';
}
// Account Holder Name validation
function validateAccountHolderName(input) {
const errorElement = document.getElementById('account-holder-name-error');
const value = input.value;
// Remove numbers and special characters (keep only letters and spaces)
const cleanValue = value.replace(/[^a-zA-Z\s]/g, '');
if (value !== cleanValue) {
input.value = cleanValue;
}
// Check content
const hasOnlyNumbers = /^[0-9]+$/.test(value.replace(/\s/g, ''));
const hasOnlySpecialChars = /^[^a-zA-Z0-9]+$/.test(value.replace(/\s/g, ''));
const hasLetters = /[a-zA-Z]/.test(value);
if (value !== cleanValue && value.length > 0) {
errorElement.textContent = 'Account holder name can only contain letters and spaces';
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else if (hasOnlyNumbers) {
errorElement.textContent = 'Account holder name cannot contain only numbers';
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else if (hasOnlySpecialChars) {
errorElement.textContent = 'Account holder name cannot contain only special characters';
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else if (!hasLetters && value.trim().length > 0) {
errorElement.textContent = 'Account holder name must include letters';
errorElement.style.display = 'block';
input.classList.add('field-error');
return false;
} else {
errorElement.style.display = 'none';
input.classList.remove('field-error');
return true;
}
}
function preventInvalidAccountHolderChars(event) {
const charCode = event.which ? event.which : event.keyCode;
const key = event.key;
if ((charCode >= 65 && charCode <= 90) ||
(charCode >= 97 && charCode <= 122) ||
charCode === 32 ||
charCode === 8 || charCode === 9 || charCode === 37 ||
charCode === 39 || charCode === 46 || charCode === 16 ||
charCode === 17 || charCode === 18 || charCode === 91 ||
/^[a-zA-Z\s]$/.test(key)) {
return true;
}
event.preventDefault();
return false;
}
// Function to generate a strong password
function generatePassword() {
const length = 12;
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*";
let password = "";
// Ensure at least one of each required character type
password += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[Math.floor(Math.random() * 26)];
password += "abcdefghijklmnopqrstuvwxyz"[Math.floor(Math.random() * 26)];
password += "0123456789"[Math.floor(Math.random() * 10)];
password += "!@#$%^&*"[Math.floor(Math.random() * 8)];
// Fill the rest randomly
for (let i = password.length; i < length; i++) {
password += charset.charAt(Math.floor(Math.random() * charset.length));
}
// Shuffle the password
password = password.split('').sort(() => 0.5 - Math.random()).join('');
// Set the password field
document.getElementById('password').value = password;
document.getElementById('confirmPassword').value = password;
// Trigger input events to update UI
document.getElementById('password').dispatchEvent(new Event('input'));
document.getElementById('confirmPassword').dispatchEvent(new Event('input'));
}
// Function to toggle password visibility
function togglePasswordVisibility() {
const passwordField = document.getElementById('password');
const confirmField = document.getElementById('confirmPassword');
const toggleButton = document.querySelector('.password-toggle');
if (passwordField.type === 'password') {
passwordField.type = 'text';
confirmField.type = 'text';
toggleButton.textContent = 'Hide Password';
} else {
passwordField.type = 'password';
confirmField.type = 'password';
toggleButton.textContent = 'Show Password';
}
}