feat: Dual-Preis-Anzeige (XMR/Fiat) und Kurs im Footer via CoinGecko

This commit is contained in:
2026-02-04 10:47:50 +01:00
parent 46c9195010
commit 28ac3e03e0
3 changed files with 53 additions and 23 deletions

View File

@@ -309,6 +309,17 @@ app-footer .footer-inner {
gap: var(--space-md); gap: var(--space-md);
} }
app-footer .xmr-rate {
font-size: var(--font-size-xs);
color: var(--color-text-muted);
opacity: 0;
transition: opacity var(--transition-fast);
}
app-footer .xmr-rate.loaded {
opacity: 1;
}
app-footer .footer-links { app-footer .footer-links {
display: flex; display: flex;
gap: var(--space-md); gap: var(--space-md);

View File

@@ -1,8 +1,33 @@
import { t } from '../i18n.js' import { t } from '../i18n.js'
import { getXmrRates, formatFiat } from '../services/currency.js'
class AppFooter extends HTMLElement { class AppFooter extends HTMLElement {
connectedCallback() { constructor() {
super()
this.xmrRate = null
}
async connectedCallback() {
this.render() this.render()
await this.loadXmrRate()
}
async loadXmrRate() {
try {
const rates = await getXmrRates()
this.xmrRate = rates.EUR
this.updateRateDisplay()
} catch (e) {
console.error('Failed to load XMR rate:', e)
}
}
updateRateDisplay() {
const rateEl = this.querySelector('.xmr-rate')
if (rateEl && this.xmrRate) {
rateEl.textContent = `1 XMR ≈ ${formatFiat(this.xmrRate, 'EUR')}`
rateEl.classList.add('loaded')
}
} }
render() { render() {
@@ -13,6 +38,7 @@ class AppFooter extends HTMLElement {
<p class="footer-copyright"> <p class="footer-copyright">
&copy; ${year} dgray.io - <span data-i18n="footer.rights">${t('footer.rights')}</span> &copy; ${year} dgray.io - <span data-i18n="footer.rights">${t('footer.rights')}</span>
</p> </p>
<span class="xmr-rate" title="CoinGecko">1 XMR ≈ ...</span>
<nav class="footer-links"> <nav class="footer-links">
<a href="#/about" data-i18n="footer.about">${t('footer.about')}</a> <a href="#/about" data-i18n="footer.about">${t('footer.about')}</a>
<a href="#/privacy" data-i18n="footer.privacy">${t('footer.privacy')}</a> <a href="#/privacy" data-i18n="footer.privacy">${t('footer.privacy')}</a>
@@ -25,6 +51,7 @@ class AppFooter extends HTMLElement {
updateTranslations() { updateTranslations() {
this.render() this.render()
if (this.xmrRate) this.updateRateDisplay()
} }
} }

View File

@@ -1,19 +1,11 @@
/** /**
* Currency Service - XMR/Fiat Conversion * Currency Service - XMR/Fiat Conversion
* *
* Uses Kraken API for real-time exchange rates * Uses CoinGecko API for real-time exchange rates (CORS-friendly)
* Supports two modes: fiat-fix and xmr-fix * Supports two modes: fiat-fix and xmr-fix
*/ */
const KRAKEN_API = 'https://api.kraken.com/0/public/Ticker' const COINGECKO_API = 'https://api.coingecko.com/api/v3/simple/price'
const PAIRS = {
USD: 'XXMRZUSD',
EUR: 'XXMRZEUR',
GBP: 'XXMRZGBP',
CHF: 'XMRCHF',
JPY: 'XMRJPY'
}
const CURRENCY_SYMBOLS = { const CURRENCY_SYMBOLS = {
XMR: 'ɱ', XMR: 'ɱ',
@@ -30,8 +22,8 @@ let cachedRates = null
let cacheTimestamp = 0 let cacheTimestamp = 0
/** /**
* Fetches current XMR rates from Kraken * Fetches current XMR rates from CoinGecko
* @returns {Promise<Object>} Rates per currency (e.g. { EUR: 150.5, USD: 165.2 }) * @returns {Promise<Object>} Rates per currency (e.g. { EUR: 329.05, USD: 388.87 })
*/ */
export async function getXmrRates() { export async function getXmrRates() {
// Check cache // Check cache
@@ -40,21 +32,21 @@ export async function getXmrRates() {
} }
try { try {
const pairs = Object.values(PAIRS).join(',') const currencies = 'eur,usd,gbp,chf,jpy'
const response = await fetch(`${KRAKEN_API}?pair=${pairs}`) const response = await fetch(`${COINGECKO_API}?ids=monero&vs_currencies=${currencies}`)
const data = await response.json() const data = await response.json()
if (data.error && data.error.length > 0) { if (!data.monero) {
console.error('Kraken API Error:', data.error) console.error('CoinGecko API Error: No data returned')
return cachedRates || getDefaultRates() return cachedRates || getDefaultRates()
} }
const rates = {} const rates = {
for (const [currency, pair] of Object.entries(PAIRS)) { EUR: data.monero.eur,
const ticker = data.result[pair] USD: data.monero.usd,
if (ticker) { GBP: data.monero.gbp,
rates[currency] = parseFloat(ticker.c[0]) // Last trade price CHF: data.monero.chf,
} JPY: data.monero.jpy
} }
// Update cache // Update cache