feat: Dual-Preis-Anzeige (XMR/Fiat) und Kurs im Footer via CoinGecko
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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">
|
||||||
© ${year} dgray.io - <span data-i18n="footer.rights">${t('footer.rights')}</span>
|
© ${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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user