Spaces:
Running
Running
| class RealTimeFeeds extends HTMLElement { | |
| constructor() { | |
| super(); | |
| this.attachShadow({ mode: 'open' }); | |
| } | |
| connectedCallback() { | |
| this.shadowRoot.innerHTML = ` | |
| <style> | |
| :host { | |
| display: block; | |
| background: #1f2937; | |
| border: 1px solid #ef4444; | |
| border-radius: 5px; | |
| padding: 15px; | |
| margin: 10px 0; | |
| min-height: 200px; | |
| } | |
| .feed-title { | |
| color: #ef4444; | |
| font-weight: bold; | |
| margin-bottom: 10px; | |
| } | |
| .price-item { | |
| display: flex; | |
| justify-content: space-between; | |
| padding: 5px 0; | |
| border-bottom: 1px solid #374151; | |
| } | |
| .price-up { | |
| color: #10b981; | |
| } | |
| .price-down { | |
| color: #ef4444; | |
| } | |
| .update-time { | |
| color: #9ca3af; | |
| font-size: 0.8rem; | |
| margin-top: 10px; | |
| } | |
| .loading { | |
| color: #9ca3af; | |
| text-align: center; | |
| padding: 20px; | |
| } | |
| .error { | |
| color: #ef4444; | |
| text-align: center; | |
| padding: 20px; | |
| } | |
| </style> | |
| <div> | |
| <h4 class="feed-title">Real-Time Crypto & Market Data</h4> | |
| <div id="feed-content"> | |
| <div class="loading">Initializing feeds...</div> | |
| </div> | |
| <div class="update-time" id="update-time"></div> | |
| </div> | |
| `; | |
| this.startRealTimeFeed(); | |
| } | |
| async startRealTimeFeed() { | |
| try { | |
| await this.updateCryptoPrices(); | |
| setInterval(() => this.updateCryptoPrices(), 60000); // Update every minute | |
| } catch (error) { | |
| this.showError('Failed to initialize feeds'); | |
| } | |
| } | |
| async updateCryptoPrices() { | |
| try { | |
| const [cryptoResponse, forexResponse] = await Promise.all([ | |
| fetch('https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum,monero,litecoin&vs_currencies=usd&include_24hr_change=true'), | |
| fetch('https://api.exchangerate-api.com/v4/latest/USD') | |
| ]); | |
| if (!cryptoResponse.ok || !forexResponse.ok) { | |
| throw new Error('API response failed'); | |
| } | |
| const cryptoPrices = await cryptoResponse.json(); | |
| const forexRates = await forexResponse.json(); | |
| this.renderContent(cryptoPrices, forexRates); | |
| } catch (error) { | |
| this.showError('Unable to fetch market data'); | |
| } | |
| } | |
| renderContent(cryptoPrices, forexRates) { | |
| const content = this.shadowRoot.getElementById('feed-content'); | |
| const updateTime = this.shadowRoot.getElementById('update-time'); | |
| const cryptoItems = Object.entries(cryptoPrices).map(([coin, data]) => { | |
| const change = data.usd_24h_change || 0; | |
| const changeClass = change >= 0 ? 'price-up' : 'price-down'; | |
| const changeSymbol = change >= 0 ? 'β' : 'β'; | |
| return ` | |
| <div class="price-item"> | |
| <span>${coin.toUpperCase()}/USD</span> | |
| <span class="${changeClass}"> | |
| $${data.usd.toLocaleString()} | |
| <span style="font-size: 0.8em; margin-left: 5px;"> | |
| ${changeSymbol} ${Math.abs(change).toFixed(2)}% | |
| </span> | |
| </span> | |
| </div> | |
| `; | |
| }).join(''); | |
| const forexItems = ['EUR', 'GBP', 'CHF', 'JPY'].map(currency => { | |
| const rate = forexRates.rates[currency]; | |
| return ` | |
| <div class="price-item"> | |
| <span>USD/${currency}</span> | |
| <span>${rate.toFixed(4)}</span> | |
| </div> | |
| `; | |
| }).join(''); | |
| content.innerHTML = ` | |
| <div style="margin-bottom: 15px;"> | |
| <h5 style="color: #ef4444; margin-bottom: 10px; font-size: 0.9em;">Cryptocurrencies</h5> | |
| ${cryptoItems} | |
| </div> | |
| <div> | |
| <h5 style="color: #ef4444; margin-bottom: 10px; font-size: 0.9em;">Forex Rates</h5> | |
| ${forexItems} | |
| </div> | |
| `; | |
| updateTime.textContent = `Last updated: ${new Date().toLocaleTimeString()}`; | |
| } | |
| showError(message) { | |
| const content = this.shadowRoot.getElementById('feed-content'); | |
| content.innerHTML = `<div class="error">${message}</div>`; | |
| } | |
| } | |
| customElements.define('real-time-feeds', RealTimeFeeds); |