"""
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 """
Geben Sie Ihre E-Mail-Adresse ein und wir senden Ihnen einen Link zum Zurücksetzen Ihres Passworts.
E-Mail wird verifiziert...
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!');
}
"""