/** * BreakPilot Customer Portal - Slim JavaScript * * Features: * - Login/Register * - My Consents View * - Data Export Request * - Legal Documents * - Theme Toggle */ // API Base URLs const CONSENT_SERVICE_URL = 'http://localhost:8081'; const BACKEND_URL = ''; // State let currentUser = null; let authToken = localStorage.getItem('bp_token'); // Initialize on DOM ready document.addEventListener('DOMContentLoaded', () => { initTheme(); initEventListeners(); checkAuth(); }); // ============================================================ // Theme // ============================================================ function initTheme() { const saved = localStorage.getItem('bp_theme') || 'light'; document.body.setAttribute('data-theme', saved); updateThemeIcon(saved); } function toggleTheme() { const current = document.body.getAttribute('data-theme'); const next = current === 'dark' ? 'light' : 'dark'; document.body.setAttribute('data-theme', next); localStorage.setItem('bp_theme', next); updateThemeIcon(next); } function updateThemeIcon(theme) { const icon = document.getElementById('theme-icon'); if (icon) icon.textContent = theme === 'dark' ? '☀️' : '🌙'; } // ============================================================ // Event Listeners // ============================================================ function initEventListeners() { // Theme toggle const themeBtn = document.getElementById('btn-theme'); if (themeBtn) themeBtn.addEventListener('click', toggleTheme); // Login button const loginBtn = document.getElementById('btn-login'); if (loginBtn) loginBtn.addEventListener('click', showLoginModal); // Legal button const legalBtn = document.getElementById('btn-legal'); if (legalBtn) legalBtn.addEventListener('click', () => showLegalDocument('privacy')); // User menu toggle const userMenuBtn = document.getElementById('user-menu-btn'); if (userMenuBtn) { userMenuBtn.addEventListener('click', () => { document.getElementById('user-menu').classList.toggle('open'); }); } // Close user menu on outside click document.addEventListener('click', (e) => { const userMenu = document.getElementById('user-menu'); if (userMenu && !userMenu.contains(e.target)) { userMenu.classList.remove('open'); } }); // Login form const loginForm = document.getElementById('login-form'); if (loginForm) loginForm.addEventListener('submit', handleLogin); // Register form const registerForm = document.getElementById('register-form'); if (registerForm) registerForm.addEventListener('submit', handleRegister); // Forgot password form const forgotForm = document.getElementById('forgot-form'); if (forgotForm) forgotForm.addEventListener('submit', handleForgotPassword); // Profile form const profileForm = document.getElementById('profile-form'); if (profileForm) profileForm.addEventListener('submit', handleProfileUpdate); // Password form const passwordForm = document.getElementById('password-form'); if (passwordForm) passwordForm.addEventListener('submit', handlePasswordChange); } // ============================================================ // Authentication // ============================================================ async function checkAuth() { if (!authToken) { showLoggedOutState(); return; } try { const res = await fetch(`${CONSENT_SERVICE_URL}/api/v1/auth/me`, { headers: { 'Authorization': `Bearer ${authToken}` } }); if (res.ok) { currentUser = await res.json(); showLoggedInState(); } else { localStorage.removeItem('bp_token'); authToken = null; showLoggedOutState(); } } catch (err) { console.error('Auth check failed:', err); showLoggedOutState(); } } function showLoggedInState() { document.getElementById('btn-login')?.classList.add('hidden'); document.getElementById('user-menu')?.classList.remove('hidden'); document.getElementById('welcome-section')?.classList.add('hidden'); document.getElementById('dashboard')?.classList.remove('hidden'); if (currentUser) { const initials = getInitials(currentUser.name || currentUser.email); document.querySelectorAll('.user-avatar').forEach(el => el.textContent = initials); document.getElementById('user-name').textContent = currentUser.name || 'Benutzer'; document.getElementById('dropdown-name').textContent = currentUser.name || 'Benutzer'; document.getElementById('dropdown-email').textContent = currentUser.email; } loadConsentCount(); } function showLoggedOutState() { document.getElementById('btn-login')?.classList.remove('hidden'); document.getElementById('user-menu')?.classList.add('hidden'); document.getElementById('welcome-section')?.classList.remove('hidden'); document.getElementById('dashboard')?.classList.add('hidden'); } function getInitials(name) { if (!name) return 'BP'; const parts = name.split(' ').filter(p => p.length > 0); if (parts.length >= 2) { return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase(); } return name.substring(0, 2).toUpperCase(); } async function handleLogin(e) { e.preventDefault(); const email = document.getElementById('login-email').value; const password = document.getElementById('login-password').value; const messageEl = document.getElementById('login-message'); try { const res = await fetch(`${CONSENT_SERVICE_URL}/api/v1/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }); const data = await res.json(); if (res.ok) { authToken = data.token; localStorage.setItem('bp_token', authToken); currentUser = data.user; showMessage(messageEl, 'Erfolgreich angemeldet!', 'success'); setTimeout(() => { closeAllModals(); showLoggedInState(); }, 500); } else { showMessage(messageEl, data.error || 'Anmeldung fehlgeschlagen', 'error'); } } catch (err) { showMessage(messageEl, 'Verbindungsfehler', 'error'); } } async function handleRegister(e) { e.preventDefault(); const name = document.getElementById('register-name').value; const email = document.getElementById('register-email').value; const password = document.getElementById('register-password').value; const passwordConfirm = document.getElementById('register-password-confirm').value; const messageEl = document.getElementById('register-message'); if (password !== passwordConfirm) { showMessage(messageEl, 'Passwörter stimmen nicht überein', 'error'); return; } try { const res = await fetch(`${CONSENT_SERVICE_URL}/api/v1/auth/register`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, email, password }) }); const data = await res.json(); if (res.ok) { showMessage(messageEl, 'Registrierung erfolgreich! Sie können sich jetzt anmelden.', 'success'); setTimeout(() => switchAuthTab('login'), 1500); } else { showMessage(messageEl, data.error || 'Registrierung fehlgeschlagen', 'error'); } } catch (err) { showMessage(messageEl, 'Verbindungsfehler', 'error'); } } async function handleForgotPassword(e) { e.preventDefault(); const email = document.getElementById('forgot-email').value; const messageEl = document.getElementById('forgot-message'); try { const res = await fetch(`${CONSENT_SERVICE_URL}/api/v1/auth/request-password-reset`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email }) }); showMessage(messageEl, 'Wenn ein Konto mit dieser E-Mail existiert, erhalten Sie einen Link zum Zurücksetzen.', 'success'); } catch (err) { showMessage(messageEl, 'Verbindungsfehler', 'error'); } } function logout() { localStorage.removeItem('bp_token'); authToken = null; currentUser = null; showLoggedOutState(); document.getElementById('user-menu')?.classList.remove('open'); } // ============================================================ // Modals // ============================================================ function showLoginModal() { document.getElementById('login-modal')?.classList.add('active'); switchAuthTab('login'); } function showMyConsents() { document.getElementById('consents-modal')?.classList.add('active'); document.getElementById('user-menu')?.classList.remove('open'); loadMyConsents(); } function showDataExport() { document.getElementById('export-modal')?.classList.add('active'); document.getElementById('user-menu')?.classList.remove('open'); loadExportRequests(); } function showAccountSettings() { document.getElementById('settings-modal')?.classList.add('active'); document.getElementById('user-menu')?.classList.remove('open'); if (currentUser) { document.getElementById('profile-name').value = currentUser.name || ''; document.getElementById('profile-email').value = currentUser.email || ''; } } function showLegalDocument(type) { const modal = document.getElementById('legal-modal'); const titleEl = document.getElementById('legal-title'); const loadingEl = document.getElementById('legal-loading'); const contentEl = document.getElementById('legal-content'); const titles = { privacy: 'Datenschutzerklärung', terms: 'Allgemeine Geschäftsbedingungen', imprint: 'Impressum', cookies: 'Cookie-Richtlinie' }; titleEl.textContent = titles[type] || 'Dokument'; loadingEl.style.display = 'block'; contentEl.innerHTML = ''; modal?.classList.add('active'); loadLegalDocument(type).then(html => { loadingEl.style.display = 'none'; contentEl.innerHTML = html; }).catch(() => { loadingEl.style.display = 'none'; contentEl.innerHTML = '
Dokument konnte nicht geladen werden.
'; }); } function showForgotPassword() { document.getElementById('login-form')?.classList.add('hidden'); document.getElementById('register-form')?.classList.add('hidden'); document.getElementById('forgot-form')?.classList.remove('hidden'); document.querySelectorAll('.auth-tab').forEach(t => t.classList.remove('active')); } function closeAllModals() { document.querySelectorAll('.modal.active').forEach(m => m.classList.remove('active')); } function switchAuthTab(tab) { document.querySelectorAll('.auth-tab').forEach(t => { t.classList.toggle('active', t.dataset.tab === tab); }); document.getElementById('login-form')?.classList.toggle('hidden', tab !== 'login'); document.getElementById('register-form')?.classList.toggle('hidden', tab !== 'register'); document.getElementById('forgot-form')?.classList.add('hidden'); // Clear messages document.querySelectorAll('.form-message').forEach(m => { m.className = 'form-message'; m.textContent = ''; }); } // ============================================================ // Consents // ============================================================ async function loadConsentCount() { if (!authToken) return; try { const res = await fetch(`${CONSENT_SERVICE_URL}/api/v1/consents/my`, { headers: { 'Authorization': `Bearer ${authToken}` } }); if (res.ok) { const data = await res.json(); const count = data.consents?.length || 0; const badge = document.getElementById('consent-count'); if (badge) badge.textContent = `${count} aktiv`; } } catch (err) { console.error('Failed to load consent count:', err); } } async function loadMyConsents() { const loadingEl = document.getElementById('consents-loading'); const listEl = document.getElementById('consents-list'); const emptyEl = document.getElementById('consents-empty'); loadingEl.style.display = 'block'; listEl.innerHTML = ''; emptyEl?.classList.add('hidden'); try { const res = await fetch(`${CONSENT_SERVICE_URL}/api/v1/consents/my`, { headers: { 'Authorization': `Bearer ${authToken}` } }); if (res.ok) { const data = await res.json(); const consents = data.consents || []; loadingEl.style.display = 'none'; if (consents.length === 0) { emptyEl?.classList.remove('hidden'); return; } listEl.innerHTML = consents.map(c => ` `).join(''); } else { loadingEl.style.display = 'none'; listEl.innerHTML = 'Fehler beim Laden der Zustimmungen.
'; } } catch (err) { loadingEl.style.display = 'none'; listEl.innerHTML = 'Verbindungsfehler.
'; } } // ============================================================ // Data Export (GDPR) // ============================================================ async function requestDataExport() { const messageEl = document.getElementById('export-message'); const btn = document.getElementById('btn-request-export'); btn.disabled = true; try { const res = await fetch(`${BACKEND_URL}/api/gdpr/request-export`, { method: 'POST', headers: { 'Authorization': `Bearer ${authToken}`, 'Content-Type': 'application/json' } }); if (res.ok) { showMessage(messageEl, 'Ihre Anfrage wurde erfolgreich eingereicht. Sie erhalten eine E-Mail, sobald der Export bereit ist.', 'success'); loadExportRequests(); } else { const data = await res.json(); showMessage(messageEl, data.error || 'Anfrage fehlgeschlagen', 'error'); } } catch (err) { showMessage(messageEl, 'Verbindungsfehler', 'error'); } finally { btn.disabled = false; } } async function loadExportRequests() { const statusEl = document.getElementById('export-status'); const listEl = document.getElementById('export-requests-list'); if (!statusEl || !listEl) return; try { const res = await fetch(`${BACKEND_URL}/api/gdpr/my-requests`, { headers: { 'Authorization': `Bearer ${authToken}` } }); if (res.ok) { const data = await res.json(); const requests = data.requests || []; if (requests.length > 0) { statusEl.classList.remove('hidden'); listEl.innerHTML = requests.map(r => ` `).join(''); } else { statusEl.classList.add('hidden'); } } } catch (err) { console.error('Failed to load export requests:', err); } } // ============================================================ // Legal Documents // ============================================================ async function loadLegalDocument(type) { try { const res = await fetch(`${CONSENT_SERVICE_URL}/api/v1/documents/type/${type}/published`); if (res.ok) { const data = await res.json(); return data.content || 'Dokument ist leer.
'; } return 'Dokument nicht gefunden.
'; } catch (err) { return 'Fehler beim Laden.
'; } } // ============================================================ // Profile & Settings // ============================================================ async function handleProfileUpdate(e) { e.preventDefault(); const name = document.getElementById('profile-name').value; const messageEl = document.getElementById('profile-message'); try { const res = await fetch(`${CONSENT_SERVICE_URL}/api/v1/auth/update-profile`, { method: 'PUT', headers: { 'Authorization': `Bearer ${authToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name }) }); if (res.ok) { currentUser.name = name; showMessage(messageEl, 'Profil aktualisiert', 'success'); showLoggedInState(); } else { showMessage(messageEl, 'Aktualisierung fehlgeschlagen', 'error'); } } catch (err) { showMessage(messageEl, 'Verbindungsfehler', 'error'); } } async function handlePasswordChange(e) { e.preventDefault(); const currentPassword = document.getElementById('current-password').value; const newPassword = document.getElementById('new-password').value; const confirmPassword = document.getElementById('new-password-confirm').value; const messageEl = document.getElementById('password-message'); if (newPassword !== confirmPassword) { showMessage(messageEl, 'Passwörter stimmen nicht überein', 'error'); return; } try { const res = await fetch(`${CONSENT_SERVICE_URL}/api/v1/auth/change-password`, { method: 'POST', headers: { 'Authorization': `Bearer ${authToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ current_password: currentPassword, new_password: newPassword }) }); if (res.ok) { showMessage(messageEl, 'Passwort geändert', 'success'); document.getElementById('password-form').reset(); } else { const data = await res.json(); showMessage(messageEl, data.error || 'Änderung fehlgeschlagen', 'error'); } } catch (err) { showMessage(messageEl, 'Verbindungsfehler', 'error'); } } function confirmDeleteAccount() { if (confirm('Sind Sie sicher, dass Sie Ihr Konto löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.')) { if (confirm('Letzte Warnung: Alle Ihre Daten werden unwiderruflich gelöscht. Fortfahren?')) { deleteAccount(); } } } async function deleteAccount() { try { const res = await fetch(`${CONSENT_SERVICE_URL}/api/v1/auth/delete-account`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${authToken}` } }); if (res.ok) { alert('Ihr Konto wurde gelöscht.'); logout(); closeAllModals(); } else { alert('Kontoloöschung fehlgeschlagen.'); } } catch (err) { alert('Verbindungsfehler'); } } // ============================================================ // Utilities // ============================================================ function showMessage(el, text, type) { if (!el) return; el.textContent = text; el.className = `form-message ${type}`; } function escapeHtml(str) { if (!str) return ''; const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } function formatDate(dateStr) { if (!dateStr) return '-'; const date = new Date(dateStr); return date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }); } // Export for global access window.showLoginModal = showLoginModal; window.showMyConsents = showMyConsents; window.showDataExport = showDataExport; window.showAccountSettings = showAccountSettings; window.showLegalDocument = showLegalDocument; window.showForgotPassword = showForgotPassword; window.closeAllModals = closeAllModals; window.switchAuthTab = switchAuthTab; window.logout = logout; window.requestDataExport = requestDataExport; window.confirmDeleteAccount = confirmDeleteAccount;