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

880 lines
35 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 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
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;
}
// Luhn algorithm
let sum = 0;
let isEven = false;
for (let i = cleanNumber.length - 1; i >= 0; i--) {
let digit = parseInt(cleanNumber.charAt(i));
if (isEven) {
digit *= 2;
if (digit > 9) {
digit -= 9;
}
}
sum += digit;
isEven = !isEven;
}
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 validateZipCode(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;
}
// Simple checksum validation
const digits = cleanRouting.split('').map(Number);
const sum = (3 * (digits[0] + digits[3] + digits[6])) +
(7 * (digits[1] + digits[4] + digits[7])) +
(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;
}
// Function to navigate to next step
function nextStep(step) {
// Validate current step before proceeding
if (!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
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;
alert('Please select a payment method');
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;
document.getElementById('card-number').style.border = '1px solid #ef4444';
alert('Please enter a valid card number');
} else {
document.getElementById('card-number').style.border = '';
// 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;
document.getElementById('expiry-date').style.border = '1px solid #ef4444';
alert('Please enter a valid expiry date (MM/YY)');
} else {
document.getElementById('expiry-date').style.border = '';
}
// CVV validation
const cvv = document.getElementById('security-code').value;
const cardType = detectCardType(cardNumber);
if (!validateCVV(cvv, cardType)) {
isValid = false;
document.getElementById('security-code').style.border = '1px solid #ef4444';
alert(`Please enter a valid ${cardType === 'amex' ? '4-digit' : '3-digit'} CVV`);
} else {
document.getElementById('security-code').style.border = '';
}
// ZIP code validation
const zipCode = document.getElementById('card-zip').value;
if (!validateZipCode(zipCode)) {
isValid = false;
document.getElementById('card-zip').style.border = '1px solid #ef4444';
alert('Please enter a valid ZIP code');
} else {
document.getElementById('card-zip').style.border = '';
}
}
// Validate bank payment
else if (formData.userDetails.paymentMethod === 'bank') {
// Account number validation
const accountNumber = document.getElementById('account-number').value;
if (!validateBankAccount(accountNumber)) {
isValid = false;
document.getElementById('account-number').style.border = '1px solid #ef4444';
alert('Please enter a valid account number (8-17 digits)');
} else {
document.getElementById('account-number').style.border = '';
}
// Routing number validation
const routingNumber = document.getElementById('routing-number').value;
if (!validateRoutingNumber(routingNumber)) {
isValid = false;
document.getElementById('routing-number').style.border = '1px solid #ef4444';
alert('Please enter a valid routing number');
} else {
document.getElementById('routing-number').style.border = '';
}
// Basic validation for other bank fields
requiredFields.forEach(field => {
if (!field.value.trim()) {
isValid = false;
field.style.border = '1px solid #ef4444';
} else {
field.style.border = '';
}
});
}
}
} else if (step === 1) {
const email = document.getElementById('email').value;
if (!validateEmail(email)) {
isValid = false;
document.getElementById('email').style.border = '1px solid #ef4444';
alert('Please enter a valid email address');
} else {
document.getElementById('email').style.border = '';
}
// Validate other required fields
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(field => {
if (field.id !== 'email' && !field.value.trim()) {
isValid = false;
field.style.border = '1px solid #ef4444';
} else if (field.id !== 'email') {
field.style.border = '';
}
});
} else if (step === 3) {
const email = document.getElementById('billing-email').value;
if (!validateEmail(email)) {
isValid = false;
document.getElementById('billing-email').style.border = '1px solid #ef4444';
alert('Please enter a valid email address');
} else {
document.getElementById('billing-email').style.border = '';
}
// Validate other required fields
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(field => {
if (field.id !== 'email' && !field.value.trim()) {
isValid = false;
field.style.border = '1px solid #ef4444';
} else if (field.id !== 'email') {
field.style.border = '';
}
});
} else if (step === 4) {
const email = document.getElementById('signatory-email').value;
if (!validateEmail(email)) {
isValid = false;
document.getElementById('signatory-email').style.border = '1px solid #ef4444';
alert('Please enter a valid email address');
} else {
document.getElementById('signatory-email').style.border = '';
}
// Validate other required fields
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(field => {
if (field.id !== 'email' && !field.value.trim()) {
isValid = false;
field.style.border = '1px solid #ef4444';
} else if (field.id !== 'email') {
field.style.border = '';
}
});
} else {
// For other steps, validate all required fields normally
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(field => {
if (!field.value.trim()) {
isValid = false;
field.style.border = '1px solid #ef4444';
} else {
field.style.border = '';
}
});
}
}
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.name = document.getElementById('company-name').value;
formData.userDetails.email = document.getElementById('email').value;
break;
case 2:
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 (!validateStep(currentStep)) {
return;
}
// Save current step data
saveStepData(currentStep);
// Save payment details based on selected method
if (formData.userDetails.paymentMethod === 'card') {
formData.userDetails.cardNumber = document.getElementById('card-number').value;
formData.userDetails.expiryDate = document.getElementById('expiry-date').value;
formData.userDetails.securityCode = document.getElementById('security-code').value;
formData.userDetails.cardZip = document.getElementById('card-zip').value;
} else if (formData.userDetails.paymentMethod === 'bank') {
formData.userDetails.bankName = document.getElementById('bank-name').value;
formData.userDetails.accountNumber = document.getElementById('account-number').value;
formData.userDetails.routingNumber = document.getElementById('routing-number').value;
formData.userDetails.accountType = document.getElementById('account-type').value;
formData.userDetails.accountHolderName = document.getElementById('account-holder-name').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 = '/client/login';
}, 1000);
} else {
// Handle server errors
if (response.status === 409) {
alert('A user with this email already exists. Please use a different email.');
} else {
alert('There was an error submitting your registration. Please try again.');
}
}
} catch (error) {
// Handle network errors
console.error('Error:', error);
alert('There was a network error. Please check your connection and try again.');
} 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');
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) {
// 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;
// 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);
}
});
}
// 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'));
}
});
// 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'));
}
});
// Detect user location when the page loads
detectLocation();
});