/** * Kasico Auction Widget - Real-time Biet-System * WebSocket-basierte Live-Auktionen mit Furry-Design */ class KasicoAuctionWidget { constructor(auctionId) { this.auctionId = auctionId; this.websocket = null; this.currentBid = null; this.timeRemaining = null; this.isConnected = false; this.bidHistory = []; this.watchers = []; this.init(); } init() { // Auction Widget HTML erstellen this.createWidget(); // Event Listeners this.bindEvents(); // WebSocket-Verbindung this.connectWebSocket(); // Initial data laden this.loadAuctionData(); // Timer starten this.startTimer(); } createWidget() { const widgetHTML = `

Lade Auktion...

Lade...
--:--:--
Aktuelles Gebot
€0.00
Keine Gebote -

Gebot-Historie

0 Gebote

Noch keine Gebote

Bieter 0
Aufrufe 0
Beobachter 0
`; // Widget in die Seite einfügen const container = document.getElementById('auction-container') || document.body; container.insertAdjacentHTML('beforeend', widgetHTML); // CSS hinzufügen this.addStyles(); } addStyles() { const styles = ` .auction-widget { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 20px; padding: 25px; color: white; font-family: 'Quicksand', sans-serif; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1); margin: 20px 0; } .auction-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 25px; padding-bottom: 20px; border-bottom: 2px solid rgba(255, 255, 255, 0.2); } .auction-title h2 { margin: 0 0 5px 0; font-size: 24px; font-weight: 700; } .auction-status { display: flex; align-items: center; gap: 8px; font-size: 14px; } .status-indicator { width: 10px; height: 10px; border-radius: 50%; background: #4CAF50; animation: pulse 2s infinite; } .status-indicator.ended { background: #f44336; animation: none; } @keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } } .auction-timer { display: flex; align-items: center; gap: 10px; background: rgba(255, 255, 255, 0.1); padding: 12px 20px; border-radius: 25px; font-weight: 600; } .timer-text { font-size: 18px; font-family: 'Courier New', monospace; } .current-bid-section { background: rgba(255, 255, 255, 0.1); border-radius: 15px; padding: 25px; margin-bottom: 25px; } .current-bid-display { text-align: center; margin-bottom: 20px; } .bid-label { font-size: 14px; opacity: 0.8; margin-bottom: 5px; } .bid-amount { font-size: 36px; font-weight: 700; margin-bottom: 10px; color: #FFD700; } .bid-info { display: flex; justify-content: center; gap: 20px; font-size: 14px; opacity: 0.8; } .bid-form { display: flex; gap: 15px; align-items: center; margin-bottom: 15px; } .bid-input-group { position: relative; flex: 1; } .currency-symbol { position: absolute; left: 15px; top: 50%; transform: translateY(-50%); font-weight: 600; color: #333; } .bid-input { width: 100%; padding: 15px 15px 15px 35px; border: none; border-radius: 25px; font-size: 16px; font-weight: 600; background: white; color: #333; outline: none; transition: all 0.3s ease; } .bid-input:focus { box-shadow: 0 0 0 3px rgba(255, 215, 0, 0.3); } .bid-button { background: linear-gradient(135deg, #FFD700, #FFA500); color: #333; border: none; border-radius: 25px; padding: 15px 25px; font-size: 16px; font-weight: 600; cursor: pointer; transition: all 0.3s ease; display: flex; align-items: center; gap: 8px; } .bid-button:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(255, 215, 0, 0.4); } .bid-button:disabled { opacity: 0.5; cursor: not-allowed; transform: none; } .reserve-info { display: flex; align-items: center; gap: 8px; font-size: 14px; opacity: 0.8; justify-content: center; } .bid-history-section { background: rgba(255, 255, 255, 0.1); border-radius: 15px; padding: 20px; margin-bottom: 25px; } .section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; } .section-header h3 { margin: 0; font-size: 18px; display: flex; align-items: center; gap: 8px; } .bid-count { font-size: 14px; opacity: 0.8; } .bid-history { max-height: 200px; overflow-y: auto; } .bid-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 0; border-bottom: 1px solid rgba(255, 255, 255, 0.1); } .bid-item:last-child { border-bottom: none; } .bid-user { font-weight: 600; } .bid-amount-small { color: #FFD700; font-weight: 600; } .bid-time-small { font-size: 12px; opacity: 0.7; } .empty-history { text-align: center; padding: 30px; opacity: 0.7; } .empty-history i { font-size: 24px; margin-bottom: 10px; } .auction-info-section { background: rgba(255, 255, 255, 0.1); border-radius: 15px; padding: 20px; margin-bottom: 25px; } .info-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; } .info-item { display: flex; align-items: center; gap: 12px; } .info-item i { font-size: 20px; color: #FFD700; } .info-content { display: flex; flex-direction: column; } .info-label { font-size: 12px; opacity: 0.8; } .info-value { font-size: 18px; font-weight: 600; } .watch-section { text-align: center; } .watch-button { background: rgba(255, 255, 255, 0.2); color: white; border: 2px solid rgba(255, 255, 255, 0.3); border-radius: 25px; padding: 12px 25px; font-size: 16px; font-weight: 600; cursor: pointer; transition: all 0.3s ease; display: inline-flex; align-items: center; gap: 8px; } .watch-button:hover { background: rgba(255, 255, 255, 0.3); border-color: rgba(255, 255, 255, 0.5); } .watch-button.watching { background: #e91e63; border-color: #e91e63; } .watch-button.watching:hover { background: #c2185b; border-color: #c2185b; } /* Responsive Design */ @media (max-width: 768px) { .auction-widget { padding: 20px; } .auction-header { flex-direction: column; gap: 15px; text-align: center; } .bid-form { flex-direction: column; } .info-grid { grid-template-columns: 1fr; gap: 15px; } } `; const styleSheet = document.createElement('style'); styleSheet.textContent = styles; document.head.appendChild(styleSheet); } bindEvents() { // Bid Button document.getElementById('place-bid-btn').addEventListener('click', () => { this.placeBid(); }); // Bid Input document.getElementById('bid-amount').addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.placeBid(); } }); // Watch Button document.getElementById('watch-button').addEventListener('click', () => { this.toggleWatch(); }); } connectWebSocket() { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/ws/auction/${this.auctionId}/`; this.websocket = new WebSocket(wsUrl); this.websocket.onopen = () => { console.log('Auction WebSocket connected'); this.isConnected = true; this.updateConnectionStatus(true); }; this.websocket.onmessage = (event) => { const data = JSON.parse(event.data); this.handleWebSocketMessage(data); }; this.websocket.onclose = () => { console.log('Auction WebSocket disconnected'); this.isConnected = false; this.updateConnectionStatus(false); // Reconnect nach 5 Sekunden setTimeout(() => { if (!this.isConnected) { this.connectWebSocket(); } }, 5000); }; this.websocket.onerror = (error) => { console.error('Auction WebSocket error:', error); }; } handleWebSocketMessage(data) { switch (data.type) { case 'bid_placed': this.handleNewBid(data.bid); break; case 'auction_updated': this.updateAuctionData(data.auction); break; case 'time_update': this.updateTimer(data.time_remaining); break; case 'auction_ended': this.handleAuctionEnded(data.winner); break; } } async loadAuctionData() { try { const response = await fetch(`/auction/api/${this.auctionId}/`); const data = await response.json(); this.updateAuctionData(data); } catch (error) { console.error('Error loading auction data:', error); } } updateAuctionData(data) { // Basic info document.getElementById('auction-title').textContent = data.title; document.getElementById('current-bid-amount').textContent = data.current_bid ? `€${parseFloat(data.current_bid).toFixed(2)}` : `€${parseFloat(data.starting_bid).toFixed(2)}`; // Status const statusElement = document.getElementById('auction-status'); const statusText = statusElement.querySelector('.status-text'); const statusIndicator = statusElement.querySelector('.status-indicator'); statusText.textContent = data.is_active ? 'Aktiv' : 'Beendet'; statusIndicator.className = `status-indicator ${data.is_active ? '' : 'ended'}`; // Reserve price if (data.reserve_price) { document.getElementById('reserve-price').textContent = parseFloat(data.reserve_price).toFixed(2); document.getElementById('reserve-info').style.display = 'flex'; } // Stats document.getElementById('total-bidders').textContent = data.total_bidders; document.getElementById('bid-count').textContent = `${data.total_bids} Gebote`; // Store current data this.currentBid = data.current_bid; this.timeRemaining = data.time_remaining; // Update timer this.updateTimer(data.time_remaining); // Load bid history this.loadBidHistory(); } async loadBidHistory() { try { const response = await fetch(`/auction/api/${this.auctionId}/bids/`); const data = await response.json(); this.updateBidHistory(data.bids); } catch (error) { console.error('Error loading bid history:', error); } } updateBidHistory(bids) { const historyContainer = document.getElementById('bid-history'); if (bids.length === 0) { historyContainer.innerHTML = `

Noch keine Gebote

`; return; } historyContainer.innerHTML = bids.map(bid => `
${bid.bidder}
€${parseFloat(bid.amount).toFixed(2)}
${this.formatTime(bid.created_at)}
`).join(''); } handleNewBid(bid) { // Update current bid document.getElementById('current-bid-amount').textContent = `€${parseFloat(bid.amount).toFixed(2)}`; document.getElementById('current-bidder').textContent = bid.bidder; document.getElementById('bid-time').textContent = this.formatTime(bid.created_at); // Add to history const historyContainer = document.getElementById('bid-history'); const bidItem = document.createElement('div'); bidItem.className = 'bid-item'; bidItem.innerHTML = `
${bid.bidder}
€${parseFloat(bid.amount).toFixed(2)}
${this.formatTime(bid.created_at)}
`; historyContainer.insertBefore(bidItem, historyContainer.firstChild); // Update stats const bidCount = document.getElementById('bid-count'); const currentCount = parseInt(bidCount.textContent.match(/\d+/)[0]); bidCount.textContent = `${currentCount + 1} Gebote`; // Animation bidItem.style.animation = 'slideIn 0.3s ease-out'; // Show notification this.showNotification(`Neues Gebot: €${parseFloat(bid.amount).toFixed(2)} von ${bid.bidder}`); } async placeBid() { const input = document.getElementById('bid-amount'); const amount = parseFloat(input.value); if (!amount || amount <= 0) { this.showError('Bitte gib ein gültiges Gebot ein'); return; } if (this.currentBid && amount <= this.currentBid) { this.showError('Gebot muss höher als das aktuelle Gebot sein'); return; } const button = document.getElementById('place-bid-btn'); const originalText = button.innerHTML; button.disabled = true; button.innerHTML = ' Sende...'; try { const response = await fetch(`/auction/api/${this.auctionId}/bid/`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': this.getCSRFToken(), }, body: JSON.stringify({ amount: amount }) }); const data = await response.json(); if (data.success) { input.value = ''; this.showSuccess('Gebot erfolgreich platziert!'); // Update via WebSocket this.websocket.send(JSON.stringify({ type: 'bid_placed', amount: amount })); } else { this.showError(data.error || 'Fehler beim Platzieren des Gebots'); } } catch (error) { this.showError('Netzwerkfehler'); } finally { button.disabled = false; button.innerHTML = originalText; } } async toggleWatch() { const button = document.getElementById('watch-button'); const originalText = button.querySelector('.watch-text').textContent; button.disabled = true; button.querySelector('.watch-text').textContent = '...'; try { const response = await fetch(`/auction/${this.auctionId}/watch/`, { method: 'POST', headers: { 'X-CSRFToken': this.getCSRFToken(), } }); const data = await response.json(); if (data.success) { if (data.is_watching) { button.classList.add('watching'); button.querySelector('.watch-text').textContent = 'Beobachtet'; } else { button.classList.remove('watching'); button.querySelector('.watch-text').textContent = 'Beobachten'; } this.showSuccess(data.message); } } catch (error) { this.showError('Fehler beim Aktualisieren der Watchlist'); } finally { button.disabled = false; } } updateTimer(timeRemaining) { const timerElement = document.getElementById('auction-timer'); const timerText = timerElement.querySelector('.timer-text'); if (!timeRemaining) { timerText.textContent = 'Beendet'; return; } timerText.textContent = timeRemaining; } handleAuctionEnded(winner) { const statusElement = document.getElementById('auction-status'); const statusText = statusElement.querySelector('.status-text'); const statusIndicator = statusElement.querySelector('.status-indicator'); statusText.textContent = 'Beendet'; statusIndicator.className = 'status-indicator ended'; // Disable bid form document.getElementById('bid-form').style.display = 'none'; // Show winner if (winner) { this.showNotification(`Auktion beendet! Gewinner: ${winner}`); } else { this.showNotification('Auktion beendet - Kein Gewinner'); } } startTimer() { setInterval(() => { if (this.timeRemaining) { // Timer logic would be implemented here // For now, just update every second } }, 1000); } updateConnectionStatus(connected) { const statusElement = document.getElementById('auction-status'); const statusIndicator = statusElement.querySelector('.status-indicator'); if (connected) { statusIndicator.style.background = '#4CAF50'; } else { statusIndicator.style.background = '#f44336'; } } formatTime(timestamp) { const date = new Date(timestamp); return date.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }); } getCSRFToken() { return document.querySelector('[name=csrfmiddlewaretoken]')?.value || document.cookie.match(/csrftoken=([^;]+)/)?.[1] || ''; } showSuccess(message) { this.showNotification(message, 'success'); } showError(message) { this.showNotification(message, 'error'); } showNotification(message, type = 'info') { // Browser notification if ('Notification' in window && Notification.permission === 'granted') { new Notification('Kasico Auction', { body: message, icon: '/static/images/kasico-logo.png' }); } // Toast notification const toast = document.createElement('div'); toast.className = `toast-notification ${type}`; toast.innerHTML = `
${type === 'success' ? '✅' : type === 'error' ? '❌' : 'ℹ️'} ${message}
`; document.body.appendChild(toast); // Auto remove after 5 seconds setTimeout(() => { toast.remove(); }, 5000); // Close button toast.querySelector('.toast-close').addEventListener('click', () => { toast.remove(); }); } } // Auction Widget initialisieren document.addEventListener('DOMContentLoaded', function() { const auctionId = document.querySelector('[data-auction-id]')?.dataset.auctionId; if (auctionId) { window.kasicoAuction = new KasicoAuctionWidget(auctionId); } });