Spaces:
Running
Running
| class NotificationSettings extends HTMLElement { | |
| constructor() { | |
| super(); | |
| this.attachShadow({ mode: 'open' }); | |
| this.config = null; | |
| this.isLoading = false; | |
| this.error = null; | |
| this.whatsAppStatusInterval = null; | |
| this.currentQrCode = null; | |
| } | |
| static get observedAttributes() { | |
| return ['loading', 'error', 'config']; | |
| } | |
| connectedCallback() { | |
| this.render(); | |
| this.loadSettings(); | |
| } | |
| disconnectedCallback() { | |
| this.stopWhatsAppPolling(); | |
| } | |
| async loadSettings() { | |
| this.isLoading = true; | |
| this.error = null; | |
| this.render(); | |
| try { | |
| // Simulated API call | |
| await new Promise(r => setTimeout(r, 800)); | |
| this.config = { | |
| telegram: { | |
| enabled: true, | |
| chatId: '-1001234567890', | |
| hasToken: true | |
| }, | |
| whatsapp: { | |
| enabled: false, | |
| gatewayUrl: 'http://localhost:3000', | |
| phoneNumber: '+1234567890', | |
| hasApiKey: false | |
| } | |
| }; | |
| this.isLoading = false; | |
| this.render(); | |
| this.startWhatsAppPolling(); | |
| } catch (err) { | |
| this.isLoading = false; | |
| this.error = err.message || 'Failed to load settings'; | |
| this.render(); | |
| } | |
| } | |
| startWhatsAppPolling() { | |
| this.refreshWhatsAppStatus(); | |
| this.whatsAppStatusInterval = setInterval(() => this.refreshWhatsAppStatus(), 3000); | |
| } | |
| stopWhatsAppPolling() { | |
| if (this.whatsAppStatusInterval) { | |
| clearInterval(this.whatsAppStatusInterval); | |
| this.whatsAppStatusInterval = null; | |
| } | |
| } | |
| async refreshWhatsAppStatus() { | |
| // Simulated status fetch | |
| const states = ['connected', 'disconnected', 'connecting']; | |
| const state = Math.random() < 0.6 ? 'connected' : (Math.random() < 0.5 ? 'connecting' : 'disconnected'); | |
| const status = { | |
| state: state, | |
| phoneNumber: state === 'connected' ? this.config?.whatsapp?.phoneNumber : null, | |
| qrCode: state === 'disconnected' ? 'mock-qr-' + Date.now() : null, | |
| timestamp: new Date().toISOString() | |
| }; | |
| this.updateWhatsAppStatusUI(status); | |
| } | |
| updateWhatsAppStatusUI(status) { | |
| const container = this.shadowRoot.getElementById('whatsapp-status-badge'); | |
| if (!container) return; | |
| const isConnected = status.state === 'connected'; | |
| const isConnecting = status.state === 'connecting'; | |
| let badgeClass = 'bg-gray-100 text-gray-800'; | |
| let dotClass = 'bg-gray-500'; | |
| let text = 'Checking...'; | |
| if (isConnected) { | |
| badgeClass = 'bg-green-100 text-green-800'; | |
| dotClass = 'bg-green-500 animate-pulse'; | |
| text = 'Connected'; | |
| } else if (isConnecting) { | |
| badgeClass = 'bg-yellow-100 text-yellow-800'; | |
| dotClass = 'bg-yellow-500 animate-pulse'; | |
| text = 'Connecting...'; | |
| } else { | |
| badgeClass = 'bg-red-100 text-red-800'; | |
| dotClass = 'bg-red-500'; | |
| text = 'Not connected'; | |
| } | |
| container.className = `inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${badgeClass}`; | |
| container.innerHTML = `<span class="w-1.5 h-1.5 ${dotClass} rounded-full mr-1.5"></span>${text}`; | |
| // Update status info | |
| const stateEl = this.shadowRoot.getElementById('status-state'); | |
| const phoneEl = this.shadowRoot.getElementById('status-phone'); | |
| const updatedEl = this.shadowRoot.getElementById('status-updated'); | |
| const qrContainer = this.shadowRoot.getElementById('qr-code-container'); | |
| const qrInstructions = this.shadowRoot.getElementById('qr-instructions'); | |
| if (stateEl) stateEl.textContent = status.state.charAt(0).toUpperCase() + status.state.slice(1); | |
| if (phoneEl) phoneEl.textContent = status.phoneNumber || '-'; | |
| if (updatedEl) updatedEl.textContent = new Date(status.timestamp).toLocaleTimeString(); | |
| // QR Code handling | |
| if (status.state === 'disconnected' && status.qrCode) { | |
| if (status.qrCode !== this.currentQrCode) { | |
| this.currentQrCode = status.qrCode; | |
| this.renderQRCode(status.qrCode); | |
| } | |
| if (qrInstructions) qrInstructions.classList.remove('hidden'); | |
| } else { | |
| this.currentQrCode = null; | |
| if (qrContainer) { | |
| if (isConnected) { | |
| qrContainer.innerHTML = ` | |
| <div class="text-center p-4"> | |
| <div class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-3"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-8 h-8 text-green-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg> | |
| </div> | |
| <p class="text-sm font-medium text-gray-900">Connected</p> | |
| <p class="text-xs text-gray-500 mt-1">${status.phoneNumber || 'WhatsApp Web active'}</p> | |
| </div> | |
| `; | |
| } else { | |
| qrContainer.innerHTML = ` | |
| <div class="text-center p-4"> | |
| <div class="w-12 h-12 border-4 border-amber-200 border-t-amber-500 rounded-full animate-spin mx-auto mb-3"></div> | |
| <p class="text-sm text-gray-500">Waiting for connection...</p> | |
| </div> | |
| `; | |
| } | |
| } | |
| if (qrInstructions) qrInstructions.classList.add('hidden'); | |
| } | |
| } | |
| renderQRCode(data) { | |
| const container = this.shadowRoot.getElementById('qr-code-container'); | |
| if (!container) return; | |
| container.innerHTML = ` | |
| <div class="w-[240px] h-[240px] bg-white rounded-lg flex items-center justify-center p-4 border-2 border-gray-200"> | |
| <div class="text-center"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-12 h-12 text-gray-400 mx-auto mb-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="3" y1="9" x2="21" y2="9"></line><line x1="9" y1="21" x2="9" y2="9"></line></svg> | |
| <p class="text-xs text-gray-500 text-center break-all">${data.slice(0, 20)}...</p> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| async saveTelegramConfig() { | |
| const enabled = this.shadowRoot.getElementById('telegram-enabled').checked; | |
| const chatId = this.shadowRoot.getElementById('telegram-chat-id').value.trim(); | |
| const token = this.shadowRoot.getElementById('telegram-bot-token').value.trim(); | |
| try { | |
| // Simulated API call | |
| await new Promise(r => setTimeout(r, 600)); | |
| this.config.telegram = { ...this.config.telegram, enabled, chatId, hasToken: !!token || this.config.telegram.hasToken }; | |
| this.dispatchEvent(new CustomEvent('toast', { | |
| detail: { type: 'success', title: 'Success', message: 'Telegram configuration saved' }, | |
| bubbles: true, | |
| composed: true | |
| })); | |
| this.shadowRoot.getElementById('telegram-bot-token').value = ''; | |
| this.render(); | |
| } catch (err) { | |
| this.dispatchEvent(new CustomEvent('toast', { | |
| detail: { type: 'error', title: 'Error', message: err.message || 'Failed to save Telegram settings' }, | |
| bubbles: true, | |
| composed: true | |
| })); | |
| } | |
| } | |
| async saveWhatsAppConfig() { | |
| const enabled = this.shadowRoot.getElementById('whatsapp-enabled').checked; | |
| const gatewayUrl = this.shadowRoot.getElementById('whatsapp-gateway').value.trim(); | |
| const phoneNumber = this.shadowRoot.getElementById('whatsapp-phone').value.trim(); | |
| const apiKey = this.shadowRoot.getElementById('whatsapp-api-key').value.trim(); | |
| try { | |
| await new Promise(r => setTimeout(r, 600)); | |
| this.config.whatsapp = { ...this.config.whatsapp, enabled, gatewayUrl, phoneNumber, hasApiKey: !!apiKey || this.config.whatsapp.hasApiKey }; | |
| this.dispatchEvent(new CustomEvent('toast', { | |
| detail: { type: 'success', title: 'Success', message: 'WhatsApp configuration saved' }, | |
| bubbles: true, | |
| composed: true | |
| })); | |
| this.shadowRoot.getElementById('whatsapp-api-key').value = ''; | |
| this.render(); | |
| } catch (err) { | |
| this.dispatchEvent(new CustomEvent('toast', { | |
| detail: { type: 'error', title: 'Error', message: err.message || 'Failed to save WhatsApp settings' }, | |
| bubbles: true, | |
| composed: true | |
| })); | |
| } | |
| } | |
| async sendTestMessage() { | |
| const phone = this.shadowRoot.getElementById('whatsapp-phone').value.trim(); | |
| const btn = this.shadowRoot.getElementById('test-message-btn'); | |
| const feedback = this.shadowRoot.getElementById('test-message-feedback'); | |
| if (!phone) return; | |
| btn.disabled = true; | |
| btn.innerHTML = '<div class="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"></div>'; | |
| try { | |
| await new Promise(r => setTimeout(r, 1000)); | |
| feedback.textContent = 'Test message sent successfully!'; | |
| feedback.className = 'mt-1 text-xs text-green-600'; | |
| this.dispatchEvent(new CustomEvent('toast', { | |
| detail: { type: 'success', title: 'Test Sent', message: 'WhatsApp test message delivered' }, | |
| bubbles: true, | |
| composed: true | |
| })); | |
| } catch (err) { | |
| feedback.textContent = `Failed: ${err.message}`; | |
| feedback.className = 'mt-1 text-xs text-red-600'; | |
| this.dispatchEvent(new CustomEvent('toast', { | |
| detail: { type: 'error', title: 'Test Failed', message: err.message }, | |
| bubbles: true, | |
| composed: true | |
| })); | |
| } finally { | |
| this.updateTestButtonState(); | |
| } | |
| } | |
| updateTestButtonState() { | |
| const phone = this.shadowRoot?.getElementById('whatsapp-phone')?.value.trim() || ''; | |
| const btn = this.shadowRoot?.getElementById('test-message-btn'); | |
| if (!btn) return; | |
| const hasPhone = phone.length > 0; | |
| btn.disabled = !hasPhone; | |
| btn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg> Test'; | |
| btn.className = hasPhone | |
| ? 'inline-flex items-center gap-2 px-4 py-2 border border-transparent rounded-lg text-sm font-medium text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition-all duration-200 active:scale-95' | |
| : 'inline-flex items-center gap-2 px-4 py-2 border border-transparent rounded-lg text-sm font-medium text-white bg-gray-400 cursor-not-allowed transition-all duration-200'; | |
| } | |
| async resetWhatsAppSession() { | |
| if (!confirm('Are you sure you want to reset the WhatsApp session? You will need to scan the QR code again.')) { | |
| return; | |
| } | |
| try { | |
| await new Promise(r => setTimeout(r, 800)); | |
| this.currentQrCode = null; | |
| this.dispatchEvent(new CustomEvent('toast', { | |
| detail: { type: 'success', title: 'Session Reset', message: 'WhatsApp session cleared. Scan the new QR code to reconnect.' }, | |
| bubbles: true, | |
| composed: true | |
| })); | |
| this.refreshWhatsAppStatus(); | |
| } catch (err) { | |
| this.dispatchEvent(new CustomEvent('toast', { | |
| detail: { type: 'error', title: 'Error', message: err.message || 'Failed to reset session' }, | |
| bubbles: true, | |
| composed: true | |
| })); | |
| } | |
| } | |
| togglePassword(inputId) { | |
| const input = this.shadowRoot.getElementById(inputId); | |
| const type = input.type === 'password' ? 'text' : 'password'; | |
| input.type = type; | |
| } | |
| refreshAll() { | |
| this.loadSettings(); | |
| } | |
| render() { | |
| const loadingHtml = ` | |
| <div class="py-16"> | |
| <div class="flex flex-col items-center justify-center"> | |
| <div class="relative"> | |
| <div class="w-16 h-16 border-4 border-amber-200 border-t-amber-500 rounded-full animate-spin"></div> | |
| <div class="absolute inset-0 flex items-center justify-center"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 text-amber-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg> | |
| </div> | |
| </div> | |
| <p class="mt-4 text-gray-600 font-medium">Loading settings...</p> | |
| </div> | |
| </div> | |
| `; | |
| const errorHtml = ` | |
| <div class="py-8"> | |
| <div class="bg-red-50 border border-red-200 rounded-xl p-6"> | |
| <div class="flex items-start gap-4"> | |
| <div class="w-10 h-10 bg-red-100 rounded-full flex items-center justify-center flex-shrink-0"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-red-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg> | |
| </div> | |
| <div> | |
| <h3 class="font-semibold text-red-800">Configuration Error</h3> | |
| <p class="text-red-600 mt-1">${this.error}</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| const contentHtml = this.config ? ` | |
| <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8 animate-fade-in"> | |
| <!-- Telegram Configuration --> | |
| <div class="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden"> | |
| <div class="px-6 py-4 border-b border-gray-200 bg-gray-50/50"> | |
| <div class="flex items-center justify-between"> | |
| <div class="flex items-center gap-3"> | |
| <div class="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-blue-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg> | |
| </div> | |
| <div> | |
| <h2 class="text-lg font-semibold text-gray-900">Telegram</h2> | |
| <p class="text-sm text-gray-500">Bot notifications via Telegram</p> | |
| </div> | |
| </div> | |
| <label class="relative inline-flex items-center cursor-pointer"> | |
| <input type="checkbox" id="telegram-enabled" class="sr-only peer" ${this.config.telegram.enabled ? 'checked' : ''}> | |
| <div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div> | |
| </label> | |
| </div> | |
| </div> | |
| <div class="p-6 space-y-4"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-2">Chat ID</label> | |
| <input type="text" id="telegram-chat-id" placeholder="Enter Telegram chat or group ID" value="${this.config.telegram.chatId}" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors"> | |
| <p class="mt-1 text-xs text-gray-500">The chat ID where notifications will be sent</p> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-2">Bot Token</label> | |
| <div class="relative"> | |
| <input type="password" id="telegram-bot-token" placeholder="Leave blank to keep existing token" class="w-full px-4 py-2 pr-10 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors"> | |
| <button type="button" class="toggle-password absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg> | |
| </button> | |
| </div> | |
| <p class="mt-1 text-xs text-gray-500">Token: ${this.config.telegram.hasToken ? 'Set' : 'Not set'}</p> | |
| </div> | |
| <div class="pt-2"> | |
| <button id="save-telegram" class="w-full inline-flex items-center justify-center gap-2 px-4 py-2 border border-transparent rounded-lg text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200 active:scale-95"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline></svg> | |
| Save Telegram Settings | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- WhatsApp Configuration --> | |
| <div class="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden"> | |
| <div class="px-6 py-4 border-b border-gray-200 bg-gray-50/50"> | |
| <div class="flex items-center justify-between"> | |
| <div class="flex items-center gap-3"> | |
| <div class="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-green-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path></svg> | |
| </div> | |
| <div> | |
| <h2 class="text-lg font-semibold text-gray-900">WhatsApp</h2> | |
| <p class="text-sm text-gray-500">Notifications via Baileys gateway</p> | |
| </div> | |
| </div> | |
| <label class="relative inline-flex items-center cursor-pointer"> | |
| <input type="checkbox" id="whatsapp-enabled" class="sr-only peer" ${this.config.whatsapp.enabled ? 'checked' : ''}> | |
| <div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-green-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-green-600"></div> | |
| </label> | |
| </div> | |
| </div> | |
| <div class="p-6 space-y-4"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-2">Gateway URL</label> | |
| <input type="text" id="whatsapp-gateway" placeholder="http://localhost:3000" value="${this.config.whatsapp.gatewayUrl}" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 transition-colors"> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-2">Recipient Phone Number</label> | |
| <div class="flex gap-2"> | |
| <input type="tel" id="whatsapp-phone" placeholder="+1234567890" value="${this.config.whatsapp.phoneNumber}" class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 transition-colors"> | |
| <button id="test-message-btn" disabled class="inline-flex items-center gap-2 px-4 py-2 border border-transparent rounded-lg text-sm font-medium text-white bg-gray-400 cursor-not-allowed transition-all duration-200"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg> | |
| Test | |
| </button> | |
| </div> | |
| <p class="mt-1 text-xs text-gray-500" id="test-message-feedback"></p> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-2">API Key (Optional)</label> | |
| <div class="relative"> | |
| <input type="password" id="whatsapp-api-key" placeholder="Leave blank to keep existing key" class="w-full px-4 py-2 pr-10 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 transition-colors"> | |
| <button type="button" class="toggle-password absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg> | |
| </button> | |
| </div> | |
| <p class="mt-1 text-xs text-gray-500">API Key: ${this.config.whatsapp.hasApiKey ? 'Set' : 'Not set'}</p> | |
| </div> | |
| <div class="flex gap-2 pt-2"> | |
| <button id="save-whatsapp" class="flex-1 inline-flex items-center justify-center gap-2 px-4 py-2 border border-transparent rounded-lg text-sm font-medium text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition-all duration-200 active:scale-95"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline></svg> | |
| Save WhatsApp | |
| </button> | |
| <button id="refresh-whatsapp" class="inline-flex items-center justify-center gap-2 px-4 py-2 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 transition-all duration-200"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 4v6h-6"></path><path d="M1 20v-6h6"></path><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg> | |
| </button> | |
| <button id="reset-whatsapp" class="inline-flex items-center justify-center gap-2 px-4 py-2 border border-red-300 rounded-lg text-sm font-medium text-red-700 bg-white hover:bg-red-50 transition-all duration-200"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- WhatsApp Status & QR Code --> | |
| <div class="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden mb-8 animate-slide-up" style="animation-delay: 0.1s;"> | |
| <div class="px-6 py-4 border-b border-gray-200 bg-gray-50/50"> | |
| <div class="flex items-center justify-between"> | |
| <h2 class="text-lg font-semibold text-gray-900">WhatsApp Connection Status</h2> | |
| <span id="whatsapp-status-badge" class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800"> | |
| <span class="w-1.5 h-1.5 bg-gray-500 rounded-full mr-1.5"></span> | |
| Checking... | |
| </span> | |
| </div> | |
| </div> | |
| <div class="p-6"> | |
| <div class="flex flex-col md:flex-row gap-6"> | |
| <div class="flex-1"> | |
| <div id="whatsapp-status-info" class="space-y-3"> | |
| <div class="flex items-center justify-between py-3 border-b border-gray-100"> | |
| <span class="text-sm text-gray-600">Connection State</span> | |
| <span id="status-state" class="text-sm font-medium text-gray-900">Unknown</span> | |
| </div> | |
| <div class="flex items-center justify-between py-3 border-b border-gray-100"> | |
| <span class="text-sm text-gray-600">Recipient Phone</span> | |
| <span id="status-phone" class="text-sm font-medium text-gray-900">-</span> | |
| </div> | |
| <div class="flex items-center justify-between py-3"> | |
| <span class="text-sm text-gray-600">Last Updated</span> | |
| <span id="status-updated" class="text-sm font-medium text-gray-900">-</span> | |
| </div> | |
| </div> | |
| <div id="qr-instructions" class="mt-4 p-4 bg-amber-50 rounded-lg border border-amber-200 hidden"> | |
| <div class="flex items-start gap-3"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-amber-600 flex-shrink-0 mt-0.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg> | |
| <div> | |
| <h4 class="text-sm font-medium text-amber-800">Scan QR Code</h4> | |
| <p class="text-sm text-amber-700 mt-1">Open WhatsApp on your phone β Settings β Linked devices β Link a device</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex-shrink-0"> | |
| <div id="qr-code-container" class="w-[260px] h-[260px] bg-gray-100 rounded-lg border-2 border-dashed border-gray-300 flex items-center justify-center"> | |
| <div class="text-center p-4"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-12 h-12 text-gray-400 mx-auto mb-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"></rect><line x1="12" y1="18" x2="12.01" y2="18"></line></svg> | |
| <p class="text-sm text-gray-500">Loading QR code...</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ` : ''; | |
| this.shadowRoot.innerHTML = ` | |
| <style> | |
| @import url('https://cdn.tailwindcss.com'); | |
| :host { | |
| display: block; | |
| } | |
| .animate-fade-in { | |
| animation: fadeIn 0.3s ease-out; | |
| } | |
| .animate-slide-up { | |
| animation: slideUp 0.4s ease-out; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| @keyframes slideUp { | |
| from { opacity: 0; transform: translateY(20px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| </style> | |
| <div class="mb-8 animate-fade-in"> | |
| <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4"> | |
| <div> | |
| <h1 class="text-3xl font-bold text-gray-900 tracking-tight">Notification Settings</h1> | |
| <p class="mt-2 text-sm text-gray-600">Configure and monitor Telegram and WhatsApp notification channels</p> | |
| </div> | |
| <div class="flex items-center gap-3"> | |
| <button id="refresh-all" class="inline-flex items-center gap-2 px-4 py-2 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-amber-500 transition-all duration-200 active:scale-95"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 4v6h-6"></path><path d="M1 20v-6h6"></path><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg> | |
| Refresh | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| ${this.isLoading ? loadingHtml : this.error ? errorHtml : contentHtml} | |
| `; | |
| // Attach event listeners | |
| if (!this.isLoading && !this.error && this.config) { | |
| this.shadowRoot.getElementById('save-telegram')?.addEventListener('click', () => this.saveTelegramConfig()); | |
| this.shadowRoot.getElementById('save-whatsapp')?.addEventListener('click', () => this.saveWhatsAppConfig()); | |
| this.shadowRoot.getElementById('test-message-btn')?.addEventListener('click', () => this.sendTestMessage()); | |
| this.shadowRoot.getElementById('refresh-whatsapp')?.addEventListener('click', () => this.refreshWhatsAppStatus()); | |
| this.shadowRoot.getElementById('reset-whatsapp')?.addEventListener('click', () => this.resetWhatsAppSession()); | |
| this.shadowRoot.getElementById('refresh-all')?.addEventListener('click', () => this.refreshAll()); | |
| this.shadowRoot.getElementById('whatsapp-phone')?.addEventListener('input', () => this.updateTestButtonState()); | |
| this.shadowRoot.querySelectorAll('.toggle-password').forEach(btn => { | |
| btn.addEventListener('click', (e) => { | |
| const inputId = e.target.closest('.relative').querySelector('input').id; | |
| this.togglePassword(inputId); | |
| }); | |
| }); | |
| // Initial button state | |
| this.updateTestButtonState(); | |
| } | |
| } | |
| } | |
| customElements.define('notification-settings', NotificationSettings); |