sentinel-hawk / components /notification-settings.js
hakandinger's picture
Notifications Screen Prompt
4db53ab verified
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);