Configurez comment l'IA se comporte avec vos clients
Modifiez les informations de votre entreprise
Changez votre mot de passe
La suppression est définitive. Toutes vos instances WhatsApp, conversations et données seront supprimées.
Aucune instance. Cliquez sur "+ Nouveau WhatsApp" pour commencer.
${inst.phone_number || 'Pas encore de numéro'} · Créée le ${new Date(inst.created_at).toLocaleDateString()}
${inst.messages_sent_today || 0} messages aujourd'hui
En attente de scan...
WhatsApp → Appareils liés → Scanner
` : 'Aucun QR code disponible
'; qrDiv.classList.remove('hidden'); document.getElementById('newInstanceName').parentElement.classList.add('hidden'); modal.classList.remove('hidden'); // Auto-polling pour détecter le scan startQRPolling(instanceId); } // ============ QR CODE AUTO-POLLING ============ let qrPollInterval = null; let qrPollInstanceId = null; function startQRPolling(instanceId) { stopQRPolling(); qrPollInstanceId = instanceId; qrPollInterval = setInterval(async () => { try { const inst = await api(`/companies/me/instances/${instanceId}`); // Si connecté → succès ! if (inst.connection_status === 'open') { stopQRPolling(); const qrDiv = document.getElementById('qrResult'); qrDiv.innerHTML = 'WhatsApp connecté avec succès !
'; qrDiv.classList.remove('hidden'); setTimeout(() => { closeCreate(); loadInstances(); loadStats(); }, 2000); return; } // Si nouveau QR disponible → mise à jour if (inst.qrcode_base64 && inst.connection_status === 'qrcode') { const qrImg = document.querySelector('#qrResult img'); if (qrImg && qrImg.src !== inst.qrcode_base64) { qrImg.src = inst.qrcode_base64; } } } catch(e) { /* silencieux */ } }, 3000); } function stopQRPolling() { if (qrPollInterval) { clearInterval(qrPollInterval); qrPollInterval = null; qrPollInstanceId = null; } } async function refreshQR(instanceId) { try { const data = await api(`/companies/me/instances/${instanceId}/qrcode`); await loadInstances(); if (data.qrcode) { showQR(instanceId); } } catch(e) { showToast('error', 'Erreur', e.message); } } // ============ PROFILE ============ async function loadProfile() { try { const p = await api('/companies/me/profile'); const setVal = (id, val) => { const el = document.getElementById(id); if (el) el.value = val || ''; }; setVal('pfName', p.commercial_name); setVal('pfRole', p.commercial_role); setVal('pfDesc', p.company_description); setVal('pfProducts', p.products_services); setVal('pfTone', p.tone || 'amical_enthousiaste'); setVal('pfSignature', p.signature); setVal('pfInstructions', p.custom_instructions); const pfCat = document.getElementById('pfUseCatalog'); if (pfCat) pfCat.checked = p.use_catalog || false; } catch(e) { console.error(e); } } async function saveProfile(e) { e.preventDefault(); try { await api('/companies/me/profile', 'PUT', { commercial_name: document.getElementById('pfName')?.value || '', commercial_role: document.getElementById('pfRole')?.value || '', company_description: document.getElementById('pfDesc')?.value || '', products_services: document.getElementById('pfProducts')?.value || '', tone: document.getElementById('pfTone')?.value || '', signature: document.getElementById('pfSignature')?.value || '', custom_instructions: document.getElementById('pfInstructions')?.value || '', use_catalog: document.getElementById('pfUseCatalog')?.checked || false, }); showToast('success', 'Profil IA', 'Profil sauvegardé !'); } catch(e) { showToast('error', 'Erreur', e.message); } } // ============ INSTANCE AI PROFILE ============ async function editInstanceProfile(instanceId, instanceName) { document.getElementById('ipmInstanceId').value = instanceId; document.getElementById('ipmInstanceName').textContent = instanceName; // Reset form ['ipmName','ipmRole','ipmDesc','ipmProducts','ipmTone','ipmSignature','ipmInstructions'].forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; }); try { const p = await api('/companies/me/instances/' + instanceId + '/profile'); const setVal = (id, val) => { const el = document.getElementById(id); if (el && val) el.value = val; }; setVal('ipmName', p.commercial_name); setVal('ipmRole', p.commercial_role); setVal('ipmDesc', p.company_description); setVal('ipmProducts', p.products_services); setVal('ipmTone', p.tone || 'amical_enthousiaste'); setVal('ipmSignature', p.signature); setVal('ipmInstructions', p.custom_instructions); const ipmCat = document.getElementById('ipmUseCatalog'); if (ipmCat) ipmCat.checked = p.use_catalog || false; } catch(e) { /* utiliser le formulaire vide */ } document.getElementById('instProfileModal').classList.remove('hidden'); lucide.createIcons(); } async function saveInstanceProfile(e) { e.preventDefault(); const instanceId = document.getElementById('ipmInstanceId').value; try { const r = await api('/companies/me/instances/' + instanceId + '/profile', 'PUT', { name: 'Instance ' + document.getElementById('ipmInstanceName').textContent, commercial_name: document.getElementById('ipmName')?.value || '', commercial_role: document.getElementById('ipmRole')?.value || '', company_description: document.getElementById('ipmDesc')?.value || '', products_services: document.getElementById('ipmProducts')?.value || '', tone: document.getElementById('ipmTone')?.value || '', signature: document.getElementById('ipmSignature')?.value || '', custom_instructions: document.getElementById('ipmInstructions')?.value || '', use_catalog: document.getElementById('ipmUseCatalog')?.checked || false, }); document.getElementById('instProfileModal').classList.add('hidden'); showToast('success', 'Profil IA', 'Personnalité appliquée à cette instance !'); } catch(e) { showToast('error', 'Erreur', e.message); } } async function resetInstanceProfile() { const instanceId = document.getElementById('ipmInstanceId').value; try { // Send empty update to unlink the profile from this instance await api('/companies/me/instances/' + instanceId + '/profile', 'PUT', { name: '', commercial_name: '', commercial_role: '', company_description: '', products_services: '', tone: '', signature: '', custom_instructions: '', }); document.getElementById('instProfileModal').classList.add('hidden'); showToast('info', 'Profil IA', 'L\'instance utilise maintenant le profil par défaut.'); } catch(e) { // Si le reset échoue, on fait un vrai reset via l'API document.getElementById('instProfileModal').classList.add('hidden'); showToast('info', 'Profil IA', 'Profil réinitialisé.'); } } // ============ COMPANY INFO ============ async function loadCompanyInfo() { try { const c = await api('/companies/me'); const setVal = (id, val) => { const el = document.getElementById(id); if (el) el.value = val || ''; }; setVal('coName', c.name); setVal('coEmail', c.email); setVal('coSector', c.sector); setVal('coWebsite', c.website); setVal('coApiKey', c.api_key); } catch(e) { console.error('loadCompanyInfo:', e); } } async function saveCompany(e) { e.preventDefault(); const saved = document.getElementById('coSaved'); try { await api('/companies/me', 'PUT', { name: document.getElementById('coName')?.value || '', sector: document.getElementById('coSector')?.value || '', website: document.getElementById('coWebsite')?.value || '' }); if (saved) { saved.style.display = 'inline'; setTimeout(() => { saved.style.display = 'none'; }, 2500); } const hdr = document.getElementById('headerCompany'); const coName = document.getElementById('coName'); if (hdr && coName) hdr.textContent = coName.value; } catch(e) { showToast('error', 'Erreur', e.message); } } // ============ PASSWORD ============ async function changePassword(e) { e.preventDefault(); const pwError = document.getElementById('pwError'); const pwSaved = document.getElementById('pwSaved'); pwError.style.display = 'none'; pwSaved.style.display = 'none'; const current = document.getElementById('pwCurrent').value; const pwNew = document.getElementById('pwNew').value; const pwConfirm = document.getElementById('pwConfirm').value; if (pwNew !== pwConfirm) { pwError.textContent = 'Les mots de passe ne correspondent pas'; pwError.style.display = 'inline'; return; } if (pwNew.length < 6) { pwError.textContent = '6 caractères minimum'; pwError.style.display = 'inline'; return; } try { await api('/companies/me/change-password', 'POST', { current_password: current, new_password: pwNew }); pwSaved.style.display = 'inline'; document.getElementById('passwordForm').reset(); setTimeout(() => { pwSaved.style.display = 'none'; }, 3000); } catch(e) { pwError.textContent = e.message.includes('Mot de passe actuel') ? 'Mot de passe actuel incorrect' : 'Erreur : ' + e.message; pwError.style.display = 'inline'; } } // ============ DELETE ACCOUNT ============ function confirmDeleteAccount() { document.getElementById('deleteBtn').classList.add('hidden'); document.getElementById('deleteConfirmBtn').classList.remove('hidden'); document.getElementById('deleteCancelBtn').classList.remove('hidden'); } function cancelDelete() { document.getElementById('deleteBtn').classList.remove('hidden'); document.getElementById('deleteConfirmBtn').classList.add('hidden'); document.getElementById('deleteCancelBtn').classList.add('hidden'); document.getElementById('deleteStatus').textContent = ''; } async function deleteAccount() { const status = document.getElementById('deleteStatus'); status.textContent = 'Suppression en cours...'; status.style.color = 'var(--muted)'; try { await api('/companies/me', 'DELETE'); // Déconnexion forcée localStorage.removeItem('wa_saas_auth'); auth = null; document.getElementById('loginPage').classList.remove('hidden'); document.getElementById('appPage').classList.add('hidden'); } catch(e) { status.textContent = 'Erreur : ' + e.message; status.style.color = 'var(--danger)'; cancelDelete(); } } async function refreshWebsite() { const status = document.getElementById('scrapeStatus'); status.innerHTML = 'Nettoyage du cache...'; try { const resp = await api('/companies/me/refresh-website', 'POST'); status.innerHTML = 'Cache vidé — prochain message = re-scrape !'; status.style.color = 'var(--success)'; setTimeout(() => { status.textContent = ''; }, 4000); } catch(e) { status.textContent = 'Erreur : ' + e.message; status.style.color = 'var(--danger)'; } } // ============ CONVERSATIONS ============ async function loadConversations(instanceId) { try { const convs = await api(`/companies/me/instances/${instanceId}/conversations`); const messages = await api(`/companies/me/instances/${instanceId}/conversations/${convs.length ? convs[0].id : ''}/messages`).catch(() => []); document.getElementById('convTitle').innerHTML = 'Conversations'; document.getElementById('convMessages').innerHTML = convs.length ? convs.map(c => `${c.last_message_at ? new Date(c.last_message_at).toLocaleString() : ''} · ${c.messages?.length || 0} msg
Aucune conversation
'; document.getElementById('convModal').classList.remove('hidden'); } catch(e) { showToast('error', 'Erreur', e.message); } } async function loadConvMessages(instanceId, convId, title) { const msgs = await api(`/companies/me/instances/${instanceId}/conversations/${convId}/messages`); document.getElementById('convTitle').innerHTML = '' + title; document.getElementById('convMessages').innerHTML = msgs.map(m => `En attente de scan...
WhatsApp → Appareils liés → Scanner
` : 'Instance créée mais pas de QR code
'; qrDiv.classList.remove('hidden'); document.getElementById('newInstanceName').parentElement.classList.add('hidden'); await loadInstances(); await loadStats(); // Auto-polling if (inst.id) startQRPolling(inst.id); } catch(e) { showToast('error', 'Erreur', e.message); } } // ============ TABS ============ function showTab(name) { document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); document.querySelectorAll('[id^="tab"]').forEach(d => d.classList.add('hidden')); document.querySelector(`[onclick="showTab('${name}')"]`).classList.add('active'); document.getElementById(`tab${name.charAt(0).toUpperCase()+name.slice(1)}`).classList.remove('hidden'); } // ============ INIT ============ // ============ CONTACT FORM ============ function showContactModal(){document.getElementById('contactModal').classList.remove('hidden');document.getElementById('contactForm').classList.remove('hidden');document.getElementById('ctSuccess').classList.add('hidden');document.getElementById('contactForm').reset();lucide.createIcons()} function closeContact(){document.getElementById('contactModal').classList.add('hidden')} async function sendContact(e){e.preventDefault();var b=document.getElementById('ctSubmit');b.disabled=true;b.innerHTML='Envoi...';lucide.createIcons();var hdrs={'Content-Type':'application/json'};if(auth&&auth.access_token)hdrs['Authorization']='Bearer '+auth.access_token;try{var r=await fetch(API+'/alerts/contact',{method:'POST',headers:hdrs,body:JSON.stringify({firstname:document.getElementById('ctFirstname').value.trim(),lastname:document.getElementById('ctLastname').value.trim(),phone:document.getElementById('ctPhone').value.trim(),message:document.getElementById('ctMessage').value.trim()})});if(!r.ok)throw new Error('err');document.getElementById('contactForm').classList.add('hidden');document.getElementById('ctSuccess').classList.remove('hidden');lucide.createIcons()}catch(ex){showToast('error','Erreur',"Impossible d'envoyer");b.disabled=false;b.innerHTML='Envoyer';lucide.createIcons()}} window.onload = function() { const saved = localStorage.getItem('wa_saas_auth'); if (saved) { // Cacher la login IMMÉDIATEMENT pour éviter le flash document.getElementById('loginPage').classList.add('hidden'); // Afficher le dashboard avec un spinner dans la zone stats document.getElementById('appPage').classList.remove('hidden'); document.getElementById('headerCompany').textContent = 'Chargement...'; document.getElementById('statsGrid').innerHTML = '