870 lines
28 KiB
JavaScript
870 lines
28 KiB
JavaScript
/**
|
||
* 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 = `
|
||
<div id="kasico-auction-widget" class="auction-widget">
|
||
<!-- Auction Header -->
|
||
<div class="auction-header">
|
||
<div class="auction-title">
|
||
<h2 id="auction-title">Lade Auktion...</h2>
|
||
<div class="auction-status" id="auction-status">
|
||
<span class="status-indicator"></span>
|
||
<span class="status-text">Lade...</span>
|
||
</div>
|
||
</div>
|
||
<div class="auction-timer" id="auction-timer">
|
||
<i class="fas fa-clock"></i>
|
||
<span class="timer-text">--:--:--</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Current Bid -->
|
||
<div class="current-bid-section">
|
||
<div class="current-bid-display">
|
||
<div class="bid-label">Aktuelles Gebot</div>
|
||
<div class="bid-amount" id="current-bid-amount">€0.00</div>
|
||
<div class="bid-info">
|
||
<span class="bidder" id="current-bidder">Keine Gebote</span>
|
||
<span class="bid-time" id="bid-time">-</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Bid Form -->
|
||
<div class="bid-form" id="bid-form">
|
||
<div class="bid-input-group">
|
||
<span class="currency-symbol">€</span>
|
||
<input type="number"
|
||
id="bid-amount"
|
||
class="bid-input"
|
||
placeholder="Dein Gebot"
|
||
min="0"
|
||
step="0.01">
|
||
</div>
|
||
<button class="bid-button" id="place-bid-btn">
|
||
<i class="fas fa-gavel"></i>
|
||
Gebot abgeben
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Reserve Price Info -->
|
||
<div class="reserve-info" id="reserve-info" style="display: none;">
|
||
<i class="fas fa-shield-alt"></i>
|
||
<span>Reserve Price: €<span id="reserve-price">0.00</span></span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Bid History -->
|
||
<div class="bid-history-section">
|
||
<div class="section-header">
|
||
<h3><i class="fas fa-history"></i> Gebot-Historie</h3>
|
||
<span class="bid-count" id="bid-count">0 Gebote</span>
|
||
</div>
|
||
<div class="bid-history" id="bid-history">
|
||
<div class="empty-history">
|
||
<i class="fas fa-gavel"></i>
|
||
<p>Noch keine Gebote</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Auction Info -->
|
||
<div class="auction-info-section">
|
||
<div class="info-grid">
|
||
<div class="info-item">
|
||
<i class="fas fa-users"></i>
|
||
<div class="info-content">
|
||
<span class="info-label">Bieter</span>
|
||
<span class="info-value" id="total-bidders">0</span>
|
||
</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<i class="fas fa-eye"></i>
|
||
<div class="info-content">
|
||
<span class="info-label">Aufrufe</span>
|
||
<span class="info-value" id="view-count">0</span>
|
||
</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<i class="fas fa-heart"></i>
|
||
<div class="info-content">
|
||
<span class="info-label">Beobachter</span>
|
||
<span class="info-value" id="watcher-count">0</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Watch Button -->
|
||
<div class="watch-section">
|
||
<button class="watch-button" id="watch-button">
|
||
<i class="fas fa-heart"></i>
|
||
<span class="watch-text">Beobachten</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// 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 = `
|
||
<div class="empty-history">
|
||
<i class="fas fa-gavel"></i>
|
||
<p>Noch keine Gebote</p>
|
||
</div>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
historyContainer.innerHTML = bids.map(bid => `
|
||
<div class="bid-item">
|
||
<div class="bid-user">${bid.bidder}</div>
|
||
<div class="bid-amount-small">€${parseFloat(bid.amount).toFixed(2)}</div>
|
||
<div class="bid-time-small">${this.formatTime(bid.created_at)}</div>
|
||
</div>
|
||
`).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 = `
|
||
<div class="bid-user">${bid.bidder}</div>
|
||
<div class="bid-amount-small">€${parseFloat(bid.amount).toFixed(2)}</div>
|
||
<div class="bid-time-small">${this.formatTime(bid.created_at)}</div>
|
||
`;
|
||
|
||
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 = '<i class="fas fa-spinner fa-spin"></i> 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 = `
|
||
<div class="toast-header">
|
||
<strong>${type === 'success' ? '✅' : type === 'error' ? '❌' : 'ℹ️'} ${message}</strong>
|
||
<button type="button" class="toast-close">×</button>
|
||
</div>
|
||
`;
|
||
|
||
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);
|
||
}
|
||
});
|