""" Auth Modal Component - Login, Register, 2FA """ def get_auth_modal_css() -> str: """CSS für Auth Modal zurückgeben""" return """ /* ========================================== AUTH MODAL STYLES ========================================== */ .auth-modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.7); z-index: 10000; justify-content: center; align-items: center; backdrop-filter: blur(4px); } .auth-modal.active { display: flex; } .auth-modal-content { background: var(--bp-surface); border-radius: 16px; width: 90%; max-width: 420px; display: flex; flex-direction: column; box-shadow: 0 20px 60px rgba(0,0,0,0.4); border: 1px solid var(--bp-border); } .auth-modal-header { display: flex; justify-content: space-between; align-items: center; padding: 20px 24px; border-bottom: 1px solid var(--bp-border); } .auth-modal-header h2 { margin: 0; font-size: 18px; font-weight: 600; display: flex; align-items: center; gap: 10px; } .auth-tabs { display: flex; gap: 4px; padding: 12px 24px; border-bottom: 1px solid var(--bp-border); background: var(--bp-surface-elevated); } .auth-tab { flex: 1; padding: 10px 16px; border: none; background: transparent; color: var(--bp-text-muted); cursor: pointer; border-radius: 8px; font-size: 14px; font-weight: 500; transition: all 0.2s ease; } .auth-tab:hover { background: var(--bp-border-subtle); color: var(--bp-text); } .auth-tab.active { background: var(--bp-primary); color: white; } .auth-body { padding: 24px; } .auth-content { display: none; } .auth-content.active { display: block; } .auth-form-group { margin-bottom: 16px; } .auth-form-label { display: block; margin-bottom: 6px; font-size: 13px; font-weight: 500; color: var(--bp-text); } .auth-form-input { width: 100%; padding: 10px 14px; border: 1px solid var(--bp-border); border-radius: 8px; background: var(--bp-surface-elevated); color: var(--bp-text); font-size: 14px; transition: border-color 0.2s ease; box-sizing: border-box; } .auth-form-input:focus { outline: none; border-color: var(--bp-primary); } .auth-form-input::placeholder { color: var(--bp-text-muted); } .auth-error { display: none; padding: 10px 14px; background: rgba(220, 53, 69, 0.15); border: 1px solid var(--bp-danger); border-radius: 8px; color: var(--bp-danger); font-size: 13px; margin-bottom: 16px; } .auth-error.active { display: block; } .auth-success { display: none; padding: 10px 14px; background: rgba(40, 167, 69, 0.15); border: 1px solid var(--bp-success); border-radius: 8px; color: var(--bp-success); font-size: 13px; margin-bottom: 16px; } .auth-success.active { display: block; } .auth-btn { width: 100%; padding: 12px 20px; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s ease; } .auth-btn-primary { background: var(--bp-primary); color: white; } .auth-btn-primary:hover { filter: brightness(1.1); } .auth-btn-primary:disabled { opacity: 0.6; cursor: not-allowed; } .auth-link { text-align: center; margin-top: 16px; font-size: 13px; color: var(--bp-text-muted); } .auth-link a { color: var(--bp-primary); text-decoration: none; cursor: pointer; } .auth-link a:hover { text-decoration: underline; } .auth-divider { display: flex; align-items: center; text-align: center; margin: 20px 0; color: var(--bp-text-muted); font-size: 12px; } .auth-divider::before, .auth-divider::after { content: ''; flex: 1; border-bottom: 1px solid var(--bp-border); } .auth-divider::before { margin-right: 12px; } .auth-divider::after { margin-left: 12px; } .auth-user-dropdown { position: relative; display: none; } .auth-user-dropdown.active { display: flex; } .auth-user-btn { display: flex; align-items: center; gap: 8px; padding: 6px 12px; background: var(--bp-surface-elevated); border: 1px solid var(--bp-border); border-radius: 8px; color: var(--bp-text); cursor: pointer; font-size: 13px; } .auth-user-btn:hover { border-color: var(--bp-primary); } .auth-user-avatar { width: 28px; height: 28px; border-radius: 50%; background: var(--bp-primary); color: white; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; } .auth-user-menu { display: none; position: absolute; top: 100%; right: 0; margin-top: 8px; background: var(--bp-surface); border: 1px solid var(--bp-border); border-radius: 8px; box-shadow: 0 10px 30px rgba(0,0,0,0.3); min-width: 200px; z-index: 1000; overflow: hidden; } .auth-user-menu.active { display: block; } .auth-user-menu-header { padding: 12px 16px; border-bottom: 1px solid var(--bp-border); } .auth-user-menu-name { font-weight: 600; font-size: 14px; color: var(--bp-text); } .auth-user-menu-email { font-size: 12px; color: var(--bp-text-muted); margin-top: 2px; } .auth-user-menu-item { display: block; width: 100%; padding: 10px 16px; border: none; background: none; text-align: left; color: var(--bp-text); cursor: pointer; font-size: 13px; transition: background 0.2s ease; } .auth-user-menu-item:hover { background: var(--bp-surface-elevated); } .auth-user-menu-item.danger { color: var(--bp-danger); } [data-theme="light"] .auth-modal-content { background: #FFFFFF; border-color: #E0E0E0; } [data-theme="light"] .auth-tabs { background: #F5F5F5; } [data-theme="light"] .auth-form-input { background: #FFFFFF; border-color: #E0E0E0; } [data-theme="light"] .auth-user-menu { background: #FFFFFF; border-color: #E0E0E0; } /* ========================================== NOTIFICATION STYLES ========================================== */ .notification-bell { position: relative; display: none; } .notification-bell.active { display: flex; } .notification-bell-btn { display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; background: var(--bp-surface-elevated); border: 1px solid var(--bp-border); border-radius: 8px; color: var(--bp-text-muted); cursor: pointer; font-size: 18px; transition: all 0.2s ease; } .notification-bell-btn:hover { border-color: var(--bp-primary); color: var(--bp-primary); } .notification-badge { position: absolute; top: 2px; right: 2px; min-width: 18px; height: 18px; background: var(--bp-danger); color: white; border-radius: 999px; font-size: 11px; font-weight: 600; display: flex; align-items: center; justify-content: center; padding: 0 5px; } .notification-badge.hidden { display: none; } .notification-panel { display: none; position: absolute; top: 100%; right: 0; margin-top: 8px; width: 360px; max-height: 480px; background: var(--bp-surface); border: 1px solid var(--bp-border); border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.3); z-index: 1000; overflow: hidden; } .notification-panel.active { display: flex; flex-direction: column; } .notification-panel-header { display: flex; justify-content: space-between; align-items: center; padding: 16px; border-bottom: 1px solid var(--bp-border); } .notification-panel-title { font-weight: 600; font-size: 16px; color: var(--bp-text); } .notification-panel-actions { display: flex; gap: 8px; } .notification-panel-action { padding: 4px 8px; background: none; border: 1px solid var(--bp-border); border-radius: 6px; color: var(--bp-text-muted); cursor: pointer; font-size: 11px; transition: all 0.2s ease; } .notification-panel-action:hover { border-color: var(--bp-primary); color: var(--bp-primary); } .notification-list { flex: 1; overflow-y: auto; max-height: 380px; } .notification-item { display: flex; gap: 12px; padding: 14px 16px; border-bottom: 1px solid var(--bp-border); cursor: pointer; transition: background 0.2s ease; } .notification-item:hover { background: var(--bp-surface-elevated); } .notification-item.unread { background: rgba(15, 118, 110, 0.08); } .notification-item.unread::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--bp-primary); } .notification-item { position: relative; } .notification-icon { width: 36px; height: 36px; border-radius: 50%; background: var(--bp-primary-soft); color: var(--bp-primary); display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0; } .notification-content { flex: 1; min-width: 0; } .notification-title { font-weight: 500; font-size: 13px; color: var(--bp-text); margin-bottom: 4px; line-height: 1.3; } .notification-body { font-size: 12px; color: var(--bp-text-muted); line-height: 1.4; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } .notification-time { font-size: 11px; color: var(--bp-text-muted); margin-top: 6px; } .notification-empty { padding: 40px 20px; text-align: center; color: var(--bp-text-muted); } .notification-empty-icon { font-size: 36px; margin-bottom: 12px; opacity: 0.5; } .notification-footer { padding: 12px 16px; border-top: 1px solid var(--bp-border); text-align: center; } .notification-footer-btn { background: none; border: none; color: var(--bp-primary); cursor: pointer; font-size: 13px; font-weight: 500; } .notification-footer-btn:hover { text-decoration: underline; } /* Notification Preferences Modal */ .notification-prefs-modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.7); z-index: 10001; justify-content: center; align-items: center; backdrop-filter: blur(4px); } .notification-prefs-modal.active { display: flex; } .notification-prefs-content { background: var(--bp-surface); border-radius: 16px; width: 90%; max-width: 420px; box-shadow: 0 20px 60px rgba(0,0,0,0.4); border: 1px solid var(--bp-border); } .notification-pref-item { display: flex; justify-content: space-between; align-items: center; padding: 14px 0; border-bottom: 1px solid var(--bp-border); } .notification-pref-item:last-child { border-bottom: none; } .notification-pref-label { font-size: 14px; color: var(--bp-text); } .notification-pref-desc { font-size: 12px; color: var(--bp-text-muted); margin-top: 2px; } .toggle-switch { position: relative; width: 48px; height: 26px; background: var(--bp-border); border-radius: 999px; cursor: pointer; transition: background 0.3s ease; } .toggle-switch.active { background: var(--bp-primary); } .toggle-switch-handle { position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; background: white; border-radius: 50%; transition: transform 0.3s ease; } .toggle-switch.active .toggle-switch-handle { transform: translateX(22px); } [data-theme="light"] .notification-panel { background: #FFFFFF; border-color: #E0E0E0; } [data-theme="light"] .notification-item.unread { background: rgba(108, 27, 27, 0.05); } [data-theme="light"] .notification-item.unread::before { background: var(--bp-primary); } /* ========================================== SUSPENSION OVERLAY STYLES ========================================== */ .suspension-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.9); z-index: 20000; justify-content: center; align-items: center; backdrop-filter: blur(8px); } .suspension-overlay.active { display: flex; } .suspension-content { background: var(--bp-surface); border-radius: 20px; width: 90%; max-width: 500px; padding: 40px; text-align: center; box-shadow: 0 20px 60px rgba(0,0,0,0.5); border: 1px solid var(--bp-danger); } .suspension-icon { font-size: 64px; margin-bottom: 20px; } .suspension-title { font-size: 24px; font-weight: 700; color: var(--bp-danger); margin-bottom: 16px; } .suspension-message { font-size: 15px; color: var(--bp-text-muted); margin-bottom: 24px; line-height: 1.6; } .suspension-docs { background: var(--bp-surface-elevated); border-radius: 12px; padding: 16px; margin-bottom: 24px; text-align: left; } .suspension-docs-title { font-size: 13px; font-weight: 600; color: var(--bp-text); margin-bottom: 12px; } .suspension-doc-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; background: var(--bp-bg); border-radius: 8px; margin-bottom: 8px; border: 1px solid var(--bp-border); } .suspension-doc-item:last-child { margin-bottom: 0; } .suspension-doc-name { font-size: 14px; color: var(--bp-text); } .suspension-doc-deadline { font-size: 12px; color: var(--bp-danger); font-weight: 500; } .suspension-btn { padding: 14px 28px; background: var(--bp-btn-primary-bg); color: white; border: none; border-radius: 10px; font-size: 15px; font-weight: 600; cursor: pointer; transition: all 0.2s ease; width: 100%; } .suspension-btn:hover { background: var(--bp-btn-primary-hover); transform: translateY(-1px); } .suspension-countdown { font-size: 12px; color: var(--bp-text-muted); margin-top: 16px; } """ def get_auth_modal_html() -> str: """HTML für Auth Modal zurückgeben""" return """

🔐 Anmeldung

Geben Sie Ihre E-Mail-Adresse ein und wir senden Ihnen einen Link zum Zurücksetzen Ihres Passworts.

E-Mail wird verifiziert...

Benachrichtigungseinstellungen

E-Mail-Benachrichtigungen
Wichtige Updates per E-Mail erhalten
In-App-Benachrichtigungen
Benachrichtigungen in der App anzeigen
Push-Benachrichtigungen
Browser-Push-Benachrichtigungen aktivieren
""" def get_auth_modal_js() -> str: """JavaScript für Auth Modal zurückgeben""" return """ const authModal = document.getElementById('auth-modal'); const authModalClose = document.getElementById('auth-modal-close'); const authTabs = document.querySelectorAll('.auth-tab'); const authContents = document.querySelectorAll('.auth-content'); const btnLogin = document.getElementById('btn-login'); // Auth state let currentUser = null; let accessToken = localStorage.getItem('bp_access_token'); let refreshToken = localStorage.getItem('bp_refresh_token'); // Update UI based on auth state function updateAuthUI() { const loginBtn = document.getElementById('btn-login'); const userDropdown = document.querySelector('.auth-user-dropdown'); const notificationBell = document.getElementById('notification-bell'); if (currentUser && accessToken) { // User is logged in - hide login button if (loginBtn) loginBtn.style.display = 'none'; // Show notification bell if (notificationBell) { notificationBell.classList.add('active'); loadNotifications(); // Load notifications on login startNotificationPolling(); // Start polling for new notifications checkSuspensionStatus(); // Check if account is suspended } // Show user dropdown if it exists if (userDropdown) { userDropdown.classList.add('active'); const avatar = userDropdown.querySelector('.auth-user-avatar'); const menuName = userDropdown.querySelector('.auth-user-menu-name'); const menuEmail = userDropdown.querySelector('.auth-user-menu-email'); if (avatar) { const initials = currentUser.name ? currentUser.name.substring(0, 2).toUpperCase() : currentUser.email.substring(0, 2).toUpperCase(); avatar.textContent = initials; } if (menuName) menuName.textContent = currentUser.name || 'Benutzer'; if (menuEmail) menuEmail.textContent = currentUser.email; } } else { // User is logged out - show login button if (loginBtn) loginBtn.style.display = 'block'; if (userDropdown) userDropdown.classList.remove('active'); if (notificationBell) notificationBell.classList.remove('active'); stopNotificationPolling(); } } // Check if user is already logged in async function checkAuthStatus() { if (!accessToken) return; try { const response = await fetch('/api/auth/profile', { headers: { 'Authorization': `Bearer ${accessToken}` } }); if (response.ok) { currentUser = await response.json(); updateAuthUI(); } else if (response.status === 401 && refreshToken) { // Try to refresh the token await refreshAccessToken(); } else { // Clear invalid tokens logout(false); } } catch (e) { console.error('Auth check failed:', e); } } // Refresh access token async function refreshAccessToken() { if (!refreshToken) return false; try { const response = await fetch('/api/auth/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refresh_token: refreshToken }) }); if (response.ok) { const data = await response.json(); accessToken = data.access_token; refreshToken = data.refresh_token; currentUser = data.user; localStorage.setItem('bp_access_token', accessToken); localStorage.setItem('bp_refresh_token', refreshToken); updateAuthUI(); return true; } else { logout(false); return false; } } catch (e) { console.error('Token refresh failed:', e); return false; } } // Logout function logout(showMessage = true) { if (refreshToken) { fetch('/api/auth/logout', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refresh_token: refreshToken }) }).catch(() => {}); } currentUser = null; accessToken = null; refreshToken = null; localStorage.removeItem('bp_access_token'); localStorage.removeItem('bp_refresh_token'); updateAuthUI(); if (showMessage) { alert('Sie wurden erfolgreich abgemeldet.'); } } // Open auth modal btnLogin?.addEventListener('click', () => { authModal.classList.add('active'); showAuthTab('login'); clearAuthErrors(); }); // Close auth modal authModalClose?.addEventListener('click', () => { authModal.classList.remove('active'); clearAuthErrors(); }); // Close on background click authModal?.addEventListener('click', (e) => { if (e.target === authModal) { authModal.classList.remove('active'); clearAuthErrors(); } }); // Tab switching authTabs.forEach(tab => { tab.addEventListener('click', () => { const tabId = tab.dataset.tab; showAuthTab(tabId); }); }); function showAuthTab(tabId) { authTabs.forEach(t => t.classList.remove('active')); authContents.forEach(c => c.classList.remove('active')); const activeTab = document.querySelector(`.auth-tab[data-tab="${tabId}"]`); if (activeTab) activeTab.classList.add('active'); document.getElementById(`auth-${tabId}`)?.classList.add('active'); clearAuthErrors(); } function clearAuthErrors() { document.querySelectorAll('.auth-error, .auth-success').forEach(el => { el.classList.remove('active'); el.textContent = ''; }); } function showAuthError(elementId, message) { const el = document.getElementById(elementId); if (el) { el.textContent = message; el.classList.add('active'); } } function showAuthSuccess(elementId, message) { const el = document.getElementById(elementId); if (el) { el.textContent = message; el.classList.add('active'); } } // Login form document.getElementById('auth-login-form')?.addEventListener('submit', async (e) => { e.preventDefault(); clearAuthErrors(); const email = document.getElementById('login-email').value; const password = document.getElementById('login-password').value; const btn = document.getElementById('login-btn'); btn.disabled = true; btn.textContent = 'Anmelden...'; try { const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }); const data = await response.json(); if (response.ok) { accessToken = data.access_token; refreshToken = data.refresh_token; currentUser = data.user; localStorage.setItem('bp_access_token', accessToken); localStorage.setItem('bp_refresh_token', refreshToken); updateAuthUI(); authModal.classList.remove('active'); // Clear form document.getElementById('login-email').value = ''; document.getElementById('login-password').value = ''; } else { showAuthError('auth-login-error', data.detail || data.error || 'Anmeldung fehlgeschlagen'); } } catch (e) { showAuthError('auth-login-error', 'Verbindungsfehler. Bitte versuchen Sie es erneut.'); } btn.disabled = false; btn.textContent = 'Anmelden'; }); // Register form document.getElementById('auth-register-form')?.addEventListener('submit', async (e) => { e.preventDefault(); clearAuthErrors(); 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; if (password !== passwordConfirm) { showAuthError('auth-register-error', 'Passwörter stimmen nicht überein'); return; } if (password.length < 8) { showAuthError('auth-register-error', 'Passwort muss mindestens 8 Zeichen lang sein'); return; } const btn = document.getElementById('register-btn'); btn.disabled = true; btn.textContent = 'Registrieren...'; try { const response = await fetch('/api/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password, name: name || undefined }) }); const data = await response.json(); if (response.ok) { showAuthSuccess('auth-register-success', 'Registrierung erfolgreich! Bitte überprüfen Sie Ihre E-Mails zur Bestätigung.'); // Clear form document.getElementById('register-name').value = ''; document.getElementById('register-email').value = ''; document.getElementById('register-password').value = ''; document.getElementById('register-password-confirm').value = ''; } else { showAuthError('auth-register-error', data.detail || data.error || 'Registrierung fehlgeschlagen'); } } catch (e) { showAuthError('auth-register-error', 'Verbindungsfehler. Bitte versuchen Sie es erneut.'); } btn.disabled = false; btn.textContent = 'Registrieren'; }); // Forgot password form document.getElementById('auth-forgot-form')?.addEventListener('submit', async (e) => { e.preventDefault(); clearAuthErrors(); const email = document.getElementById('forgot-email').value; const btn = document.getElementById('forgot-btn'); btn.disabled = true; btn.textContent = 'Senden...'; try { const response = await fetch('/api/auth/forgot-password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email }) }); // Always show success to prevent email enumeration showAuthSuccess('auth-forgot-success', 'Falls ein Konto mit dieser E-Mail existiert, wurde ein Link zum Zurücksetzen gesendet.'); document.getElementById('forgot-email').value = ''; } catch (e) { showAuthError('auth-forgot-error', 'Verbindungsfehler. Bitte versuchen Sie es erneut.'); } btn.disabled = false; btn.textContent = 'Link senden'; }); // Reset password form document.getElementById('auth-reset-form')?.addEventListener('submit', async (e) => { e.preventDefault(); clearAuthErrors(); const password = document.getElementById('reset-password').value; const passwordConfirm = document.getElementById('reset-password-confirm').value; const token = document.getElementById('reset-token').value; if (password !== passwordConfirm) { showAuthError('auth-reset-error', 'Passwörter stimmen nicht überein'); return; } if (password.length < 8) { showAuthError('auth-reset-error', 'Passwort muss mindestens 8 Zeichen lang sein'); return; } const btn = document.getElementById('reset-btn'); btn.disabled = true; btn.textContent = 'Ändern...'; try { const response = await fetch('/api/auth/reset-password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token, new_password: password }) }); const data = await response.json(); if (response.ok) { showAuthSuccess('auth-reset-success', 'Passwort erfolgreich geändert! Sie können sich jetzt anmelden.'); // Clear URL params window.history.replaceState({}, document.title, window.location.pathname); // Switch to login after 2 seconds setTimeout(() => showAuthTab('login'), 2000); } else { showAuthError('auth-reset-error', data.detail || data.error || 'Passwort zurücksetzen fehlgeschlagen'); } } catch (e) { showAuthError('auth-reset-error', 'Verbindungsfehler. Bitte versuchen Sie es erneut.'); } btn.disabled = false; btn.textContent = 'Passwort ändern'; }); // Navigation links document.getElementById('auth-forgot-password')?.addEventListener('click', (e) => { e.preventDefault(); showAuthTab('forgot'); // Hide tabs for forgot password document.querySelector('.auth-tabs').style.display = 'none'; }); document.getElementById('auth-back-to-login')?.addEventListener('click', (e) => { e.preventDefault(); showAuthTab('login'); document.querySelector('.auth-tabs').style.display = 'flex'; }); document.getElementById('auth-goto-login')?.addEventListener('click', (e) => { e.preventDefault(); showAuthTab('login'); }); // Check for URL parameters (email verification, password reset) function checkAuthUrlParams() { const urlParams = new URLSearchParams(window.location.search); const verifyToken = urlParams.get('verify'); const resetToken = urlParams.get('reset'); if (verifyToken) { authModal.classList.add('active'); document.querySelector('.auth-tabs').style.display = 'none'; showAuthTab('verify'); verifyEmail(verifyToken); } else if (resetToken) { authModal.classList.add('active'); document.querySelector('.auth-tabs').style.display = 'none'; showAuthTab('reset'); document.getElementById('reset-token').value = resetToken; } } async function verifyEmail(token) { try { const response = await fetch('/api/auth/verify-email', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token }) }); const data = await response.json(); const loadingEl = document.getElementById('auth-verify-loading'); if (response.ok) { if (loadingEl) loadingEl.style.display = 'none'; showAuthSuccess('auth-verify-success', 'E-Mail erfolgreich verifiziert! Sie können sich jetzt anmelden.'); // Clear URL params window.history.replaceState({}, document.title, window.location.pathname); // Switch to login after 2 seconds setTimeout(() => { showAuthTab('login'); document.querySelector('.auth-tabs').style.display = 'flex'; }, 2000); } else { if (loadingEl) loadingEl.style.display = 'none'; showAuthError('auth-verify-error', data.detail || data.error || 'Verifizierung fehlgeschlagen. Der Link ist möglicherweise abgelaufen.'); } } catch (e) { document.getElementById('auth-verify-loading').style.display = 'none'; showAuthError('auth-verify-error', 'Verbindungsfehler. Bitte versuchen Sie es erneut.'); } } // User dropdown toggle const authUserBtn = document.getElementById('auth-user-btn'); const authUserMenu = document.getElementById('auth-user-menu'); authUserBtn?.addEventListener('click', (e) => { e.stopPropagation(); authUserMenu.classList.toggle('active'); }); // Close dropdown when clicking outside document.addEventListener('click', () => { authUserMenu?.classList.remove('active'); }); // Placeholder functions for profile/sessions function showProfileModal() { alert('Profil-Einstellungen kommen bald!'); } function showSessionsModal() { alert('Sitzungsverwaltung kommt bald!'); } """