// LOBOR FX Get Quote Page - Calculator with Live Exchange Rate API + Global Language Selector
console.log('Calculator script loaded');
// Global Language Selector Class
if (!window.LoborLanguageSelector) {
class LoborLanguageSelector {
constructor() {
this.currentLanguage = localStorage.getItem('loborLanguage') || 'es';
this.languageMenuButton = null;
this.languageDropdown = null;
this.initialized = false;
// Bind methods to preserve context
this.toggleLanguageMenu = this.toggleLanguageMenu.bind(this);
this.selectLanguage = this.selectLanguage.bind(this);
this.handleOutsideClick = this.handleOutsideClick.bind(this);
this.reinitialize = this.reinitialize.bind(this);
}
init() {
if (this.initialized) {
console.log('LanguageSelector already initialized, cleaning up first...');
this.cleanup();
}
console.log('Initializing Enhanced Language Selector...');
// Set initial body class for language
document.body.className = document.body.className.replace(/lang-\w+/g, '').trim();
document.body.classList.add(`lang-${this.currentLanguage}`);
// Use MutationObserver to detect DOM changes
this.observeDOM();
setTimeout(() => {
this.setupElements();
this.attachEventListeners();
this.applyCurrentLanguage();
this.initialized = true;
console.log('Language Selector initialized successfully');
}, 300);
}
observeDOM() {
// Create an observer to watch for DOM changes (page navigations)
if (this.observer) {
this.observer.disconnect();
}
this.observer = new MutationObserver((mutations) => {
let shouldReinit = false;
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
// Check if navigation elements were added/removed
const hasLanguageElements = document.querySelector('#language-menu-button, .language-option');
if (hasLanguageElements && !this.languageMenuButton) {
shouldReinit = true;
}
}
});
if (shouldReinit) {
console.log('DOM changes detected, reinitializing language selector...');
setTimeout(this.reinitialize, 200);
}
});
// Start observing
this.observer.observe(document.body, {
childList: true,
subtree: true
});
}
reinitialize() {
if (!this.initialized) {
this.init();
} else {
this.setupElements();
this.attachEventListeners();
this.applyCurrentLanguage();
}
}
setupElements() {
// Desktop elements
this.languageMenuButton = document.getElementById('language-menu-button');
this.languageDropdown = document.getElementById('language-dropdown');
console.log('Desktop - Language button found:', !!this.languageMenuButton);
console.log('Desktop - Language dropdown found:', !!this.languageDropdown);
}
attachEventListeners() {
// Remove existing listeners first
this.removeEventListeners();
// Desktop dropdown toggle
if (this.languageMenuButton) {
this.languageMenuButton.addEventListener('click', this.toggleLanguageMenu);
console.log('Added click listener to desktop language button');
}
// Get all language options (both desktop and mobile)
const allLanguageOptions = document.querySelectorAll('.language-option');
console.log('Found language options:', allLanguageOptions.length);
// Store references to remove listeners later
this.languageOptions = Array.from(allLanguageOptions);
// Add listeners to all language options
this.languageOptions.forEach((option, index) => {
// Add multiple event types for better compatibility
option.addEventListener('click', this.selectLanguage, { passive: false });
option.addEventListener('touchstart', this.selectLanguage, { passive: false });
console.log(`Added listeners to language option ${index}:`, option.dataset.lang);
});
// Close dropdown when clicking outside (desktop only)
document.addEventListener('click', this.handleOutsideClick);
console.log('Event listeners attached successfully');
}
removeEventListeners() {
if (this.languageMenuButton) {
this.languageMenuButton.removeEventListener('click', this.toggleLanguageMenu);
}
if (this.languageOptions) {
this.languageOptions.forEach(option => {
option.removeEventListener('click', this.selectLanguage);
option.removeEventListener('touchstart', this.selectLanguage);
});
}
document.removeEventListener('click', this.handleOutsideClick);
}
toggleLanguageMenu(event) {
event.preventDefault();
event.stopPropagation();
console.log('Toggle language menu clicked');
if (this.languageDropdown) {
const isHidden = this.languageDropdown.classList.contains('hidden');
if (isHidden) {
this.languageDropdown.classList.remove('hidden');
console.log('Showing dropdown');
} else {
this.languageDropdown.classList.add('hidden');
console.log('Hiding dropdown');
}
}
}
selectLanguage(event) {
event.preventDefault();
event.stopPropagation();
let target = event.target;
console.log('selectLanguage called, target:', target.tagName, target.textContent?.substring(0, 20));
// Find the closest language-option element
while (target && !target.classList.contains('language-option')) {
target = target.parentElement;
if (!target || target === document.body) {
console.log('No language-option found in parent hierarchy');
return;
}
}
if (!target) {
console.log('No target found');
return;
}
const selectedLang = target.dataset.lang;
console.log('Language selected:', selectedLang, 'Current:', this.currentLanguage);
if (selectedLang && selectedLang !== this.currentLanguage) {
this.currentLanguage = selectedLang;
localStorage.setItem('loborLanguage', selectedLang);
this.updateCurrentLanguageDisplay();
this.switchPageLanguage();
console.log('Language switched to:', this.currentLanguage);
// Dispatch custom event for other components to listen
window.dispatchEvent(new CustomEvent('languageChanged', {
detail: { language: selectedLang }
}));
}
// Close dropdown if it exists (desktop only)
if (this.languageDropdown) {
this.languageDropdown.classList.add('hidden');
}
}
updateCurrentLanguageDisplay() {
const currentLangElement = document.getElementById('current-language');
if (currentLangElement) {
currentLangElement.textContent = this.currentLanguage.toUpperCase();
console.log('Updated language display to:', this.currentLanguage.toUpperCase());
}
}
switchPageLanguage() {
console.log('Switching page language to:', this.currentLanguage);
// Remove existing language classes and add new one
document.body.className = document.body.className.replace(/lang-\w+/g, '').trim();
document.body.classList.add(`lang-${this.currentLanguage}`);
console.log('Applied body class:', `lang-${this.currentLanguage}`);
// Fallback method using the old system for compatibility
const allSpanishContent = document.querySelectorAll('.es-content');
const allEnglishContent = document.querySelectorAll('.en-content');
console.log('Found Spanish elements:', allSpanishContent.length);
console.log('Found English elements:', allEnglishContent.length);
if (this.currentLanguage === 'es') {
// Show Spanish content using style
allSpanishContent.forEach((element) => {
element.style.display = '';
element.classList.remove('hidden');
});
// Hide English content using style
allEnglishContent.forEach((element) => {
element.style.display = 'none';
element.classList.add('hidden');
});
} else {
// Show English content using style
allEnglishContent.forEach((element) => {
element.style.display = '';
element.classList.remove('hidden');
});
// Hide Spanish content using style
allSpanishContent.forEach((element) => {
element.style.display = 'none';
element.classList.add('hidden');
});
}
console.log('Language switch completed using both methods');
}
applyCurrentLanguage() {
console.log('Applying current language:', this.currentLanguage);
this.updateCurrentLanguageDisplay();
this.switchPageLanguage();
}
handleOutsideClick(event) {
if (this.languageDropdown && this.languageMenuButton &&
!this.languageDropdown.contains(event.target) &&
!this.languageMenuButton.contains(event.target)) {
this.languageDropdown.classList.add('hidden');
}
}
cleanup() {
console.log('Cleaning up Language Selector...');
this.removeEventListeners();
if (this.observer) {
this.observer.disconnect();
}
this.initialized = false;
}
}
window.LoborLanguageSelector = new LoborLanguageSelector();
}
// Enhanced calculator with live exchange rate API
function initEnhancedCalculator() {
console.log('Initializing enhanced calculator with live API...');
const API_KEY = 'f13af1e60efa6bcf5b8e0260';
const BASE_URL = 'https://v6.exchangerate-api.com/v6';
let currentExchangeRate = 19.50; // Fallback rate
let lastUpdated = null;
// Wait for DOM
const transactionInput = document.getElementById('transaction-amount');
const exchangeRateInput = document.getElementById('exchange-rate');
const calculateButton = document.getElementById('calculate-savings');
const resultsSection = document.getElementById('results-section');
const rateInfoElement = document.getElementById('exchange-rate-info');
console.log('Elements found:', {
transactionInput: !!transactionInput,
exchangeRateInput: !!exchangeRateInput,
calculateButton: !!calculateButton,
resultsSection: !!resultsSection,
rateInfoElement: !!rateInfoElement
});
if (!transactionInput || !calculateButton) {
console.error('Critical elements not found');
return;
}
// Load live exchange rate
async function loadLiveExchangeRate() {
if (rateInfoElement) {
rateInfoElement.innerHTML = 'Obteniendo tipo de cambio en tiempo real...';
}
try {
const response = await fetch(`${BASE_URL}/${API_KEY}/pair/USD/MXN`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.result === 'success') {
currentExchangeRate = data.conversion_rate;
lastUpdated = new Date(data.time_last_update_utc);
if (rateInfoElement) {
const timeString = lastUpdated.toLocaleString('es-ES', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
rateInfoElement.innerHTML = `
Tipo de cambio oficial: $${currentExchangeRate.toFixed(4)} MXN/USD
Actualizado: ${timeString} UTC - Fuente: ExchangeRate-API
`;
}
if (exchangeRateInput) {
exchangeRateInput.placeholder = currentExchangeRate.toFixed(4);
}
console.log('Live exchange rate loaded successfully:', currentExchangeRate);
} else {
throw new Error(`API error: ${data['error-type']}`);
}
} catch (error) {
console.error('Error loading exchange rate:', error);
if (rateInfoElement) {
rateInfoElement.innerHTML = `
Usando tipo de cambio estimado: $${currentExchangeRate.toFixed(4)} MXN/USD
No se pudo obtener la tasa en vivo. Ingrese la de su banco para mayor precisión.
`;
}
}
}
// Load exchange rate on init
loadLiveExchangeRate();
// Format numbers with commas
function formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
// Parse input removing commas
function parseInput(str) {
if (!str) return 0;
return parseFloat(str.replace(/,/g, '')) || 0;
}
// Format transaction amount input
function formatTransactionInput(event) {
let value = event.target.value.replace(/[^\d]/g, '');
if (value) {
value = formatNumber(parseInt(value));
}
event.target.value = value;
}
// Format exchange rate input
function formatRateInput(event) {
let value = event.target.value.replace(/[^\d.]/g, '');
const parts = value.split('.');
if (parts.length > 2) {
value = parts[0] + '.' + parts[1];
}
if (parts[1] && parts[1].length > 4) {
value = parts[0] + '.' + parts[1].substring(0, 4);
}
event.target.value = value;
}
// Main calculation function with live rates
function calculateSavings() {
console.log('Calculate button clicked with live rates');
const button = calculateButton;
button.disabled = true;
// Get current language from body class
const currentLang = document.body.classList.contains('lang-en') ? 'en' : 'es';
button.innerHTML = currentLang === 'es' ?
'CALCULANDO CON TASAS REALES...' :
'CALCULATING WITH REAL RATES...';
try {
// Get values
const transactionAmount = parseInput(transactionInput.value);
let exchangeRate = parseInput(exchangeRateInput.value);
console.log('Values:', { transactionAmount, exchangeRate, liveRate: currentExchangeRate });
// Validation
if (!transactionAmount || transactionAmount <= 0) {
const alertMessage = currentLang === 'es' ?
'Por favor ingrese un monto válido' :
'Please enter a valid amount';
alert(alertMessage);
transactionInput.focus();
return;
}
// Use live rate if empty, otherwise use user's bank rate
if (!exchangeRate) {
exchangeRate = currentExchangeRate;
}
// Calculate costs using live exchange rate for more precision
const bankCostUSD = transactionAmount * 0.018; // 1.8% bank cost
const loborCostUSD = transactionAmount * 0.008; // 0.8% LOBOR FX cost
const savingsUSD = bankCostUSD - loborCostUSD;
// Convert to MXN using the rate
const bankCostMXN = bankCostUSD * exchangeRate;
const loborCostMXN = loborCostUSD * exchangeRate;
const savingsMXN = savingsUSD * exchangeRate;
const savingsPercent = ((savingsUSD / bankCostUSD) * 100).toFixed(1);
const annualSavingsUSD = savingsUSD * 12;
const annualSavingsMXN = annualSavingsUSD * exchangeRate;
console.log('Enhanced Results:', {
bankCostUSD, loborCostUSD, savingsUSD,
bankCostMXN, loborCostMXN, savingsMXN,
exchangeRate, savingsPercent
});
// Update results with both USD and MXN
const updates = {
'savings-amount': `$${formatNumber(Math.round(savingsUSD))} USD / $${formatNumber(Math.round(savingsMXN))} MXN`,
'bank-cost': `~$${formatNumber(Math.round(bankCostUSD))} USD / ~$${formatNumber(Math.round(bankCostMXN))} MXN`,
'lobor-cost': `$${formatNumber(Math.round(loborCostUSD))} USD / $${formatNumber(Math.round(loborCostMXN))} MXN`,
'transaction-display': `$${formatNumber(transactionAmount)} USD`,
'savings-percentage': `${savingsPercent}%`,
'annual-savings': `$${formatNumber(Math.round(annualSavingsUSD))} USD / $${formatNumber(Math.round(annualSavingsMXN))} MXN`
};
// Apply updates
for (const [id, value] of Object.entries(updates)) {
const element = document.getElementById(id);
if (element) {
element.innerHTML = value.replace(' / ', '
');
if (value.includes(' / ')) {
element.innerHTML += '';
}
console.log(`Updated ${id}: ${value}`);
} else {
console.warn(`Element ${id} not found`);
}
}
// Show exchange rate used in results
const rateUsedText = exchangeRate === currentExchangeRate ?
`Tipo de cambio oficial: $${exchangeRate.toFixed(4)}` :
`Tipo de cambio de su banco: $${exchangeRate.toFixed(4)}`;
// Add rate info to results if there's a place for it
const transactionDetails = document.getElementById('transaction-display');
if (transactionDetails && transactionDetails.parentNode) {
const rateInfo = transactionDetails.parentNode.querySelector('.rate-info');
if (!rateInfo) {
const rateDiv = document.createElement('div');
rateDiv.className = 'rate-info text-xs opacity-75 mt-1';
rateDiv.textContent = rateUsedText;
transactionDetails.parentNode.appendChild(rateDiv);
}
}
// Show results
if (resultsSection) {
resultsSection.classList.remove('hidden');
setTimeout(() => {
resultsSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, 300);
}
console.log('Enhanced calculation completed successfully');
} catch (error) {
console.error('Calculation error:', error);
const alertMessage = currentLang === 'es' ?
'Error en el cálculo: ' + error.message :
'Calculation error: ' + error.message;
alert(alertMessage);
} finally {
// Re-enable button
setTimeout(() => {
button.disabled = false;
const buttonText = currentLang === 'es' ? {
long: 'CALCULAR MI AHORRO AHORA',
short: 'CALCULAR AHORRO'
} : {
long: 'CALCULATE MY SAVINGS NOW',
short: 'CALCULATE SAVINGS'
};
button.innerHTML = `
${buttonText.long}
${buttonText.short}
`;
}, 1500);
}
}
// Add event listeners
transactionInput.addEventListener('input', formatTransactionInput);
if (exchangeRateInput) {
exchangeRateInput.addEventListener('input', formatRateInput);
}
// Multiple event types for better mobile support
calculateButton.addEventListener('click', calculateSavings);
calculateButton.addEventListener('touchend', function(e) {
e.preventDefault();
setTimeout(calculateSavings, 100);
});
// Enter key support
transactionInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
calculateSavings();
}
});
if (exchangeRateInput) {
exchangeRateInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
calculateSavings();
}
});
}
// Auto-refresh exchange rate every 5 minutes
setInterval(loadLiveExchangeRate, 5 * 60 * 1000);
console.log('Enhanced calculator initialized successfully');
}
// Initialize immediately when script loads
export function init() {
console.log('Initializing Get Quote page with language selector and enhanced calculator...');
// Initialize global language selector
window.LoborLanguageSelector.init();
// Initialize enhanced calculator
if (document.readyState === 'complete') {
initEnhancedCalculator();
} else if (document.readyState === 'interactive') {
setTimeout(initEnhancedCalculator, 100);
} else {
document.addEventListener('DOMContentLoaded', initEnhancedCalculator);
// Backup timer in case DOMContentLoaded doesn't fire
setTimeout(initEnhancedCalculator, 1000);
}
// Listen for language change events
window.addEventListener('languageChanged', (event) => {
console.log('Language changed event received:', event.detail.language);
});
}
// Cleanup
export function teardown() {
console.log('Tearing down Get Quote page...');
// Don't cleanup the global selector, let it persist
}