/** * Kasico Chat Widget - Furry-Style Live-Chat * WebSocket-basiertes Chat-System */ class KasicoChatWidget { constructor() { this.isOpen = false; this.isConnected = false; this.currentRoom = null; this.websocket = null; this.notificationSocket = null; this.typingTimeout = null; this.unreadCount = 0; this.init(); } init() { // Chat-Widget HTML erstellen this.createWidget(); // Event Listeners this.bindEvents(); // WebSocket-Verbindung this.connectWebSocket(); // Auto-Show nach 30 Sekunden setTimeout(() => { if (!this.isOpen && !this.hasInteracted()) { this.showWelcomeMessage(); } }, 30000); } createWidget() { const widgetHTML = `
Chat Online
Kasico Support

Kasico Support

Online
Willkommen bei Kasico! 🐾

Wie können wir dir heute helfen?

`; document.body.insertAdjacentHTML('beforeend', widgetHTML); // CSS hinzufügen this.addStyles(); } addStyles() { const styles = ` .chat-widget { position: fixed; bottom: 20px; right: 20px; z-index: 1000; font-family: 'Quicksand', sans-serif; } .chat-button { background: linear-gradient(135deg, #FF6B9D, #FF8E53); color: white; border: none; border-radius: 25px; padding: 15px 20px; cursor: pointer; display: flex; align-items: center; gap: 12px; box-shadow: 0 4px 20px rgba(255, 107, 157, 0.3); transition: all 0.3s ease; min-width: 200px; } .chat-button:hover { transform: translateY(-2px); box-shadow: 0 6px 25px rgba(255, 107, 157, 0.4); } .chat-button-icon { position: relative; font-size: 20px; } .chat-notification-badge { position: absolute; top: -8px; right: -8px; background: #FF4757; color: white; border-radius: 50%; width: 20px; height: 20px; font-size: 12px; display: flex; align-items: center; justify-content: center; font-weight: bold; } .chat-button-text { display: flex; flex-direction: column; align-items: flex-start; } .chat-button-title { font-weight: 600; font-size: 16px; } .chat-button-subtitle { font-size: 12px; opacity: 0.9; } .chat-window { position: absolute; bottom: 80px; right: 0; width: 350px; height: 500px; background: white; border-radius: 20px; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1); display: none; flex-direction: column; overflow: hidden; border: 2px solid #FF6B9D; } .chat-header { background: linear-gradient(135deg, #FF6B9D, #FF8E53); color: white; padding: 15px 20px; display: flex; justify-content: space-between; align-items: center; } .chat-header-info { display: flex; align-items: center; gap: 12px; } .chat-avatar { width: 40px; height: 40px; border-radius: 50%; overflow: hidden; border: 2px solid white; } .chat-avatar img { width: 100%; height: 100%; object-fit: cover; } .chat-header-text h4 { margin: 0; font-size: 16px; font-weight: 600; } .chat-status { font-size: 12px; opacity: 0.9; } .chat-status.online::before { content: ''; display: inline-block; width: 8px; height: 8px; background: #2ED573; border-radius: 50%; margin-right: 5px; } .chat-header-actions { display: flex; gap: 8px; } .chat-header-actions button { background: rgba(255, 255, 255, 0.2); border: none; color: white; width: 30px; height: 30px; border-radius: 50%; cursor: pointer; transition: all 0.3s ease; } .chat-header-actions button:hover { background: rgba(255, 255, 255, 0.3); } .chat-messages { flex: 1; padding: 20px; overflow-y: auto; background: #F8F9FA; } .chat-welcome { text-align: center; padding: 20px; background: white; border-radius: 15px; margin-bottom: 20px; } .chat-welcome-icon { font-size: 40px; color: #FF6B9D; margin-bottom: 15px; } .chat-welcome h5 { margin: 0 0 10px 0; color: #333; } .chat-welcome p { margin: 0 0 20px 0; color: #666; } .chat-quick-actions { display: flex; flex-direction: column; gap: 8px; } .quick-action { background: linear-gradient(135deg, #FF6B9D, #FF8E53); color: white; border: none; border-radius: 20px; padding: 10px 15px; cursor: pointer; font-size: 14px; transition: all 0.3s ease; display: flex; align-items: center; gap: 8px; } .quick-action:hover { transform: translateY(-1px); box-shadow: 0 4px 15px rgba(255, 107, 157, 0.3); } .chat-message { margin-bottom: 15px; display: flex; flex-direction: column; } .chat-message.sent { align-items: flex-end; } .chat-message.received { align-items: flex-start; } .chat-message.system { align-items: center; } .message-bubble { max-width: 80%; padding: 12px 16px; border-radius: 20px; font-size: 14px; line-height: 1.4; position: relative; } .message-bubble.sent { background: linear-gradient(135deg, #FF6B9D, #FF8E53); color: white; border-bottom-right-radius: 5px; } .message-bubble.received { background: white; color: #333; border: 1px solid #E9ECEF; border-bottom-left-radius: 5px; } .message-bubble.system { background: #F8F9FA; color: #666; font-size: 12px; font-style: italic; border-radius: 15px; } .message-time { font-size: 11px; opacity: 0.7; margin-top: 5px; } .message-time.sent { text-align: right; } .message-time.received { text-align: left; } .message-time.system { text-align: center; } .chat-input-container { padding: 15px 20px; background: white; border-top: 1px solid #E9ECEF; } .chat-input-wrapper { display: flex; align-items: flex-end; gap: 10px; } .chat-input { flex: 1; border: 1px solid #E9ECEF; border-radius: 20px; padding: 12px 16px; font-size: 14px; resize: none; outline: none; transition: border-color 0.3s ease; font-family: inherit; } .chat-input:focus { border-color: #FF6B9D; } .chat-input-actions { display: flex; gap: 8px; } .chat-input-actions button { background: linear-gradient(135deg, #FF6B9D, #FF8E53); color: white; border: none; width: 40px; height: 40px; border-radius: 50%; cursor: pointer; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; } .chat-input-actions button:hover { transform: scale(1.1); } .chat-input-actions button:disabled { opacity: 0.5; cursor: not-allowed; } .chat-typing { padding: 10px 20px; display: flex; align-items: center; gap: 10px; color: #666; font-size: 12px; } .typing-indicator { display: flex; gap: 3px; } .typing-indicator span { width: 6px; height: 6px; background: #FF6B9D; border-radius: 50%; animation: typing 1.4s infinite ease-in-out; } .typing-indicator span:nth-child(1) { animation-delay: -0.32s; } .typing-indicator span:nth-child(2) { animation-delay: -0.16s; } @keyframes typing { 0%, 80%, 100% { transform: scale(0.8); opacity: 0.5; } 40% { transform: scale(1); opacity: 1; } } .chat-widget.hidden .chat-window { display: none; } .chat-widget.open .chat-window { display: flex; } /* Responsive Design */ @media (max-width: 768px) { .chat-widget { bottom: 10px; right: 10px; } .chat-window { width: calc(100vw - 20px); height: calc(100vh - 100px); bottom: 70px; } .chat-button { min-width: auto; padding: 12px 15px; } .chat-button-text { display: none; } } `; const styleSheet = document.createElement('style'); styleSheet.textContent = styles; document.head.appendChild(styleSheet); } bindEvents() { // Chat Button document.getElementById('chat-button').addEventListener('click', () => { this.toggleChat(); }); // Chat Controls document.getElementById('chat-minimize').addEventListener('click', () => { this.toggleChat(); }); document.getElementById('chat-close').addEventListener('click', () => { this.closeChat(); }); // Chat Input const chatInput = document.getElementById('chat-input'); chatInput.addEventListener('keypress', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.sendMessage(); } }); chatInput.addEventListener('input', () => { this.adjustTextareaHeight(chatInput); this.sendTypingIndicator(true); }); // Send Button document.getElementById('chat-send').addEventListener('click', () => { this.sendMessage(); }); // Attachment Button document.getElementById('chat-attachment').addEventListener('click', () => { document.getElementById('chat-file-input').click(); }); // File Upload document.getElementById('chat-file-input').addEventListener('change', (e) => { this.handleFileUpload(e.target.files[0]); }); // Quick Actions document.querySelectorAll('.quick-action').forEach(button => { button.addEventListener('click', (e) => { const action = e.target.closest('.quick-action').dataset.action; this.handleQuickAction(action); }); }); } connectWebSocket() { // Chat WebSocket this.connectChatWebSocket(); // Notification WebSocket this.connectNotificationWebSocket(); } connectChatWebSocket() { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/ws/chat/${this.currentRoom || 'new'}/`; this.websocket = new WebSocket(wsUrl); this.websocket.onopen = () => { console.log('Chat 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('Chat WebSocket disconnected'); this.isConnected = false; this.updateConnectionStatus(false); // Reconnect nach 5 Sekunden setTimeout(() => { if (!this.isConnected) { this.connectChatWebSocket(); } }, 5000); }; this.websocket.onerror = (error) => { console.error('Chat WebSocket error:', error); }; } connectNotificationWebSocket() { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/ws/notifications/`; this.notificationSocket = new WebSocket(wsUrl); this.notificationSocket.onopen = () => { console.log('Notification WebSocket connected'); }; this.notificationSocket.onmessage = (event) => { const data = JSON.parse(event.data); this.handleNotificationMessage(data); }; } handleWebSocketMessage(data) { switch (data.type) { case 'message': this.addMessage(data.message); break; case 'typing': this.showTypingIndicator(data.user, data.typing); break; case 'read': this.markMessagesAsRead(data.user_id); break; case 'system': this.addSystemMessage(data.message); break; case 'error': this.showError(data.message); break; } } handleNotificationMessage(data) { if (data.type === 'notification') { this.showNotification(data.notification); } } sendMessage() { const input = document.getElementById('chat-input'); const message = input.value.trim(); if (!message || !this.isConnected) return; // Nachricht senden this.websocket.send(JSON.stringify({ type: 'message', content: message, message_type: 'text' })); // Input leeren input.value = ''; this.adjustTextareaHeight(input); // Typing Indicator stoppen this.sendTypingIndicator(false); } sendTypingIndicator(typing) { if (this.typingTimeout) { clearTimeout(this.typingTimeout); } if (typing) { this.websocket.send(JSON.stringify({ type: 'typing', typing: true })); this.typingTimeout = setTimeout(() => { this.sendTypingIndicator(false); }, 3000); } else { this.websocket.send(JSON.stringify({ type: 'typing', typing: false })); } } addMessage(message) { const messagesContainer = document.getElementById('chat-messages'); const messageDiv = document.createElement('div'); const isOwnMessage = message.sender_id === this.getCurrentUserId(); const messageClass = isOwnMessage ? 'sent' : 'received'; messageDiv.className = `chat-message ${messageClass}`; let messageContent = ''; if (message.message_type === 'image') { messageContent = `Bild`; } else if (message.message_type === 'file') { messageContent = `
${message.content}
`; } else { messageContent = message.content; } messageDiv.innerHTML = `
${messageContent}
${this.formatTime(message.created_at)}
`; messagesContainer.appendChild(messageDiv); this.scrollToBottom(); // Notification Badge aktualisieren if (!isOwnMessage && !this.isOpen) { this.incrementUnreadCount(); } } addSystemMessage(message) { const messagesContainer = document.getElementById('chat-messages'); const messageDiv = document.createElement('div'); messageDiv.className = 'chat-message system'; messageDiv.innerHTML = `
${message}
${this.formatTime(new Date())}
`; messagesContainer.appendChild(messageDiv); this.scrollToBottom(); } showTypingIndicator(user, typing) { const typingElement = document.getElementById('chat-typing'); if (typing) { typingElement.style.display = 'flex'; } else { typingElement.style.display = 'none'; } } showWelcomeMessage() { this.addSystemMessage('Willkommen! Wie können wir dir helfen? 🐾'); } handleQuickAction(action) { const messages = { 'pricing': 'Hier findest du unsere Preise: https://kasico.de/pricing', 'custom-order': 'Für Custom Orders: https://kasico.de/custom-order', 'shipping': 'Versandinfo: https://kasico.de/shipping' }; this.addMessage({ sender_id: this.getCurrentUserId(), content: messages[action], message_type: 'text', created_at: new Date().toISOString() }); // Nachricht senden this.websocket.send(JSON.stringify({ type: 'message', content: messages[action], message_type: 'text' })); } handleFileUpload(file) { if (!file) return; // Datei-Upload simulieren (in Produktion würde hier ein echter Upload stattfinden) const fileUrl = URL.createObjectURL(file); this.websocket.send(JSON.stringify({ type: 'file_upload', file_url: fileUrl, filename: file.name, file_type: file.type.startsWith('image/') ? 'image' : 'file' })); } toggleChat() { const widget = document.getElementById('kasico-chat-widget'); if (this.isOpen) { widget.classList.remove('open'); this.isOpen = false; } else { widget.classList.add('open'); this.isOpen = true; this.clearUnreadCount(); this.scrollToBottom(); } } closeChat() { const widget = document.getElementById('kasico-chat-widget'); widget.classList.remove('open'); this.isOpen = false; } updateConnectionStatus(connected) { const statusElement = document.querySelector('.chat-status'); const button = document.getElementById('chat-button'); if (connected) { statusElement.textContent = 'Online'; statusElement.className = 'chat-status online'; button.style.opacity = '1'; } else { statusElement.textContent = 'Offline'; statusElement.className = 'chat-status offline'; button.style.opacity = '0.7'; } } incrementUnreadCount() { this.unreadCount++; const badge = document.getElementById('chat-notification-badge'); badge.textContent = this.unreadCount; badge.style.display = 'flex'; } clearUnreadCount() { this.unreadCount = 0; const badge = document.getElementById('chat-notification-badge'); badge.style.display = 'none'; } scrollToBottom() { const messagesContainer = document.getElementById('chat-messages'); messagesContainer.scrollTop = messagesContainer.scrollHeight; } adjustTextareaHeight(textarea) { textarea.style.height = 'auto'; textarea.style.height = Math.min(textarea.scrollHeight, 100) + 'px'; } formatTime(timestamp) { const date = new Date(timestamp); return date.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }); } getCurrentUserId() { // In einer echten Implementierung würde hier die User-ID aus dem Session/Token geholt return 1; // Placeholder } hasInteracted() { // Prüfen ob User bereits mit der Seite interagiert hat return sessionStorage.getItem('chat_interaction') === 'true'; } showError(message) { this.addSystemMessage(`Fehler: ${message}`); } showNotification(notification) { // Browser Notification anzeigen if ('Notification' in window && Notification.permission === 'granted') { new Notification(notification.title, { body: notification.message, icon: '/static/images/kasico-logo.png' }); } } } // Chat Widget initialisieren document.addEventListener('DOMContentLoaded', function() { window.kasicoChat = new KasicoChatWidget(); // Notification Permission anfordern if ('Notification' in window && Notification.permission === 'default') { Notification.requestPermission(); } });