From 96538ab1db234bda4987f6c09e9b03cea3b14a1a Mon Sep 17 00:00:00 2001 From: Alexander Schmidt Date: Wed, 4 Feb 2026 11:06:16 +0100 Subject: [PATCH] feat: add offline indicator and increase CoinGecko cache to avoid rate limits --- js/components/error-boundary.js | 70 +++++++++++++++++++++++++++++++++ js/services/currency.js | 4 +- service-worker.js | 4 +- 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/js/components/error-boundary.js b/js/components/error-boundary.js index 0efeac9..862754b 100644 --- a/js/components/error-boundary.js +++ b/js/components/error-boundary.js @@ -88,6 +88,42 @@ export function setupGlobalErrorHandler() { console.error('Unhandled promise rejection:', event.reason) showErrorToast(event.reason?.message || 'Ein Fehler ist aufgetreten') }) + + // Offline/Online detection + setupOfflineIndicator() +} + +/** + * Shows/hides offline indicator based on network status + */ +function setupOfflineIndicator() { + const updateStatus = () => { + let indicator = document.querySelector('.offline-indicator') + + if (!navigator.onLine) { + if (!indicator) { + indicator = document.createElement('div') + indicator.className = 'offline-indicator' + indicator.innerHTML = ` + 📡 + Offline + ` + document.body.appendChild(indicator) + requestAnimationFrame(() => indicator.classList.add('visible')) + } + } else { + if (indicator) { + indicator.classList.remove('visible') + setTimeout(() => indicator.remove(), 300) + } + } + } + + window.addEventListener('online', updateStatus) + window.addEventListener('offline', updateStatus) + + // Check on init + updateStatus() } /** @@ -214,6 +250,40 @@ style.textContent = /* css */` .error-toast-close:hover { color: var(--color-text); } + + /* Offline Indicator */ + .offline-indicator { + position: fixed; + top: var(--space-md); + left: 50%; + transform: translateX(-50%) translateY(-100px); + display: flex; + align-items: center; + gap: var(--space-xs); + padding: var(--space-xs) var(--space-md); + background: var(--color-bg-secondary); + border: 1px solid var(--color-border); + border-radius: var(--radius-full); + box-shadow: var(--shadow-md); + z-index: 10001; + opacity: 0; + transition: transform 0.3s ease, opacity 0.3s ease; + } + + .offline-indicator.visible { + transform: translateX(-50%) translateY(0); + opacity: 1; + } + + .offline-indicator .offline-icon { + filter: grayscale(1); + } + + .offline-indicator .offline-text { + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + color: var(--color-text); + } ` document.head.appendChild(style) diff --git a/js/services/currency.js b/js/services/currency.js index f8c3e91..2c0ba66 100644 --- a/js/services/currency.js +++ b/js/services/currency.js @@ -16,8 +16,8 @@ const CURRENCY_SYMBOLS = { JPY: '¥' } -const CACHE_DURATION = 10 * 60 * 1000 // 10 minutes (CoinGecko free tier: 10-30 req/min) -const MIN_REQUEST_INTERVAL = 6 * 1000 // 6 seconds between requests (max ~10/min) +const CACHE_DURATION = 30 * 60 * 1000 // 30 minutes (CoinGecko free tier is strict) +const MIN_REQUEST_INTERVAL = 60 * 1000 // 60 seconds between requests let cachedRates = null let cacheTimestamp = 0 diff --git a/service-worker.js b/service-worker.js index 9dedd69..ef96578 100644 --- a/service-worker.js +++ b/service-worker.js @@ -1,4 +1,4 @@ -const CACHE_NAME = 'dgray-v27'; +const CACHE_NAME = 'dgray-v28'; const STATIC_ASSETS = [ '/', '/index.html', @@ -16,6 +16,8 @@ const STATIC_ASSETS = [ '/js/components/chat-widget.js', '/js/components/listing-card.js', '/js/components/search-box.js', + '/js/components/error-boundary.js', + '/js/services/currency.js', '/locales/de.json', '/locales/en.json', '/locales/fr.json',