import {revive, persist, labelInput, labelSelect} from '/js/util.js'; export default { id:'currency', name:'Currency Converter', about:'Convert between currencies using real-time exchange rates.', render(root){ const key='calc_currency_v1'; const s = revive(key,{amount:100, from:'USD', to:'EUR', manualRate: null}); const ui = document.createElement('div'); // Popular currencies with better formatting const currencies = [ ['USD', 'US Dollar'], ['EUR', 'Euro'], ['GBP', 'British Pound'], ['JPY', 'Japanese Yen'], ['CAD', 'Canadian Dollar'], ['AUD', 'Australian Dollar'], ['CHF', 'Swiss Franc'], ['CNY', 'Chinese Yuan'], ['INR', 'Indian Rupee'], ['BRL', 'Brazilian Real'] ]; // Create a more user-friendly form layout const formContainer = document.createElement('div'); formContainer.style.cssText = ` display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 20px; `; // Amount input (full width) const amountDiv = document.createElement('div'); amountDiv.style.gridColumn = '1 / -1'; amountDiv.innerHTML = ` `; // From currency (left column) - using Select Lite const fromDiv = document.createElement('div'); fromDiv.innerHTML = ` `; // To currency (right column) - using Select Lite const toDiv = document.createElement('div'); toDiv.innerHTML = ` `; formContainer.append(amountDiv, fromDiv, toDiv); ui.append(formContainer); // Manual rate input option const manualRateDiv = document.createElement('div'); manualRateDiv.innerHTML = `
Format: 1 ${s.from} = X ${s.to}
Example: If 1 USD = 0.85 EUR, enter 0.85
Leave empty to use current market rates
`; ui.append(manualRateDiv); // Fetch rates button with better styling const fetchBtn = document.createElement('button'); fetchBtn.type = 'button'; fetchBtn.textContent = 'Update Exchange Rates'; fetchBtn.className = 'btn'; fetchBtn.style.cssText = ` width: 100%; padding: 12px; margin: 15px 0; font-size: 16px; font-weight: 500; background: var(--accent); color: white; border: none; border-radius: 8px; cursor: pointer; transition: background 0.2s; `; fetchBtn.addEventListener('mouseenter', () => fetchBtn.style.opacity = '0.9'); fetchBtn.addEventListener('mouseleave', () => fetchBtn.style.opacity = '1'); ui.append(fetchBtn); // External API notice const noticeDiv = document.createElement('div'); noticeDiv.style.cssText = ` margin: 10px 0; padding: 12px; background: var(--k-bg); border: 1px solid var(--border); border-radius: 6px; font-size: 13px; color: var(--muted); line-height: 1.4; `; noticeDiv.innerHTML = ` External API Notice: Updating exchange rates requires an external call to exchangerate-api.com. Your browser will make a request to fetch the latest rates. Your IP address will be visible to the API provider. `; ui.append(noticeDiv); const out = document.createElement('div'); out.className = 'result'; out.style.cssText = ` margin: 20px 0; padding: 15px; background: var(--k-bg); border-radius: 8px; border-left: 4px solid var(--accent); `; ui.append(out); const status = document.createElement('div'); status.className='status'; ui.append(status); // Static rates from today (2025-09-01) - no prefetching const staticRates = { USD: 1, EUR: 0.856, GBP: 0.741, JPY: 147.15, CAD: 1.37, AUD: 1.53, CHF: 0.801, CNY: 7.13, INR: 88.19, BRL: 5.43 }; let rates = staticRates; let lastFetch = 0; const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes // Function to get current static rate for currency pair function getCurrentStaticRate(from, to) { if (from === to) return 1; if (rates && rates[from] && rates[to]) { return rates[to] / rates[from]; } return null; } // Function to update custom rate with current static rate function updateCustomRateWithStatic() { const from = ui.querySelector('[name=from]').value; const to = ui.querySelector('[name=to]').value; const staticRate = getCurrentStaticRate(from, to); if (staticRate !== null) { const manualRateInput = ui.querySelector('[name=manualRate]'); // Update manual rate to current market rate when fetching new rates manualRateInput.value = staticRate.toFixed(4); manualRateInput.placeholder = `Current rate: ${staticRate.toFixed(4)}`; // Trigger calculation with the new rates calc(); } } async function fetchRates(){ const now = Date.now(); if (rates && (now - lastFetch) < CACHE_DURATION) { status.textContent = 'Using cached rates (updated within 5 minutes)'; status.className = 'status success'; status.style.display = 'block'; setTimeout(() => { status.textContent = ''; status.style.display = 'none'; }, 3000); return rates; } try { status.textContent = 'Fetching latest exchange rates...'; status.className = 'status loading'; const response = await fetch('https://api.exchangerate-api.com/v4/latest/USD'); if (!response.ok) throw new Error('Failed to fetch rates'); const data = await response.json(); rates = data.rates; rates.USD = 1; // Base currency lastFetch = now; status.textContent = 'Rates updated successfully!'; status.className = 'status success'; status.style.display = 'block'; setTimeout(() => { status.textContent = ''; status.style.display = 'none'; }, 1500); // Update custom rate with new rates and recalculate updateCustomRateWithStatic(); return rates; } catch (error) { console.error('Failed to fetch exchange rates:', error); status.textContent = 'Failed to fetch rates. Using static rates from today.'; status.className = 'status error'; // Keep using static rates return rates; } } function calc(){ const amount = +ui.querySelector('[name=amount]').value || 0; const from = ui.querySelector('[name=from]').value; const to = ui.querySelector('[name=to]').value; const manualRate = +ui.querySelector('[name=manualRate]').value || null; console.log('Calc function called with:', { amount, from, to, manualRate }); if (amount <= 0) { out.innerHTML = '
Enter a positive amount to convert
'; return; } if (from === to) { out.innerHTML = `
${amount.toFixed(2)} ${from}
Same currency - no conversion needed
`; return; } let conversionRate; let rateSource; let rateDate; if (manualRate && manualRate > 0) { console.log('Using manual rate:', manualRate); // Use manual rate conversionRate = manualRate; // Determine if this is user-entered or default custom rate const staticRate = staticRates[to] / staticRates[from]; const isUserEntered = Math.abs(manualRate - staticRate) > 0.0001; // Allow for floating point precision rateSource = isUserEntered ? 'Custom rate' : 'Static rates'; rateDate = isUserEntered ? 'Manual input' : '2025-09-01'; out.innerHTML = `
${(amount * conversionRate).toFixed(2)} ${to}
Conversion: ${amount} ${from} × ${conversionRate.toFixed(4)} = ${(amount * conversionRate).toFixed(2)} ${to}
Rate: 1 ${from} = ${conversionRate.toFixed(4)} ${to}
Source: ${rateSource}
Date: ${rateDate}
`; } else if (rates) { console.log('Using market rates, manual rate was:', manualRate); // Use fetched or static rates (fallback when no manual rate) conversionRate = rates[to] / rates[from]; rateSource = lastFetch > 0 ? 'Live rates' : 'Static rates'; rateDate = lastFetch > 0 ? new Date(lastFetch).toLocaleString() : '2025-09-01'; out.innerHTML = `
${(amount * conversionRate).toFixed(2)} ${to}
Conversion: ${amount} ${from} × ${conversionRate.toFixed(4)} = ${(amount * conversionRate).toFixed(2)} ${to}
Rate: 1 ${from} = ${conversionRate.toFixed(4)} ${to}
Rate: 1 ${to} = ${(1/conversionRate).toFixed(4)} ${from}
Source: ${rateSource}
Rates accurate as of: ${rateDate}
`; } else { out.innerHTML = '
Unable to calculate conversion
'; return; } persist(key, {amount, from, to, manualRate}); } // Manual rate input handler const manualRateInput = manualRateDiv.querySelector('[name="manualRate"]'); manualRateInput.value = s.manualRate || ''; // Debug logging console.log('Setting up manual rate input handler'); // Set up the event listener manualRateInput.addEventListener('input', (e) => { console.log('Manual rate input event fired:', e.target.value); // Clear any existing status messages status.textContent = ''; status.style.display = 'none'; // Update the stored manual rate s.manualRate = +e.target.value || null; console.log('Updated manual rate to:', s.manualRate); // Simple test - just update the display immediately if (s.manualRate && s.manualRate > 0) { const amount = +ui.querySelector('[name=amount]').value || 0; const from = ui.querySelector('[name=from]').value; const to = ui.querySelector('[name=to]').value; console.log('Immediate calculation test:', { amount, from, to, manualRate: s.manualRate }); // Determine if this is user-entered or default custom rate const staticRate = staticRates[to] / staticRates[from]; const isUserEntered = Math.abs(s.manualRate - staticRate) > 0.0001; // Allow for floating point precision const rateSource = isUserEntered ? 'Custom rate' : 'Static rates'; // Show immediate result out.innerHTML = `
${(amount * s.manualRate).toFixed(2)} ${to}
Conversion: ${amount} ${from} × ${s.manualRate.toFixed(4)} = ${(amount * s.manualRate).toFixed(2)} ${to}
Rate: 1 ${from} = ${s.manualRate.toFixed(4)} ${to}
Source: ${rateSource}
`; } else { // Fall back to normal calculation calc(); } }); // Also add change event as backup manualRateInput.addEventListener('change', (e) => { console.log('Manual rate change event fired:', e.target.value); s.manualRate = +e.target.value || null; calc(); }); // Currency change handlers const fromSelect = fromDiv.querySelector('[name=from]'); const toSelect = toDiv.querySelector('[name=to]'); fromSelect.addEventListener('change', () => { s.from = fromSelect.value; // Update manual rate description and custom rate const desc = manualRateDiv.querySelector('div'); desc.innerHTML = desc.innerHTML.replace(/1 [A-Z]{3} = X [A-Z]{3}/, `1 ${s.from} = X ${s.to}`); updateCustomRateWithStatic(); calc(); }); toSelect.addEventListener('change', () => { s.to = toSelect.value; // Update manual rate description and custom rate const desc = manualRateDiv.querySelector('div'); desc.innerHTML = desc.innerHTML.replace(/1 [A-Z]{3} = X [A-Z]{3}/, `1 ${s.from} = X ${s.to}`); updateCustomRateWithStatic(); calc(); }); // Amount input handler const amountInput = amountDiv.querySelector('[name="amount"]'); amountInput.addEventListener('input', calc); // Fetch rates button click handler fetchBtn.addEventListener('click', async () => { await fetchRates(); calc(); // Recalculate with new rates }); // Initialize custom rate with current static rate updateCustomRateWithStatic(); // Initial calculation calc(); root.append(ui); } }