This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/backend/frontend/modules/mail_inbox.py
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

1999 lines
53 KiB
Python

"""
BreakPilot Studio - Unified Inbox Mail Modul
Dieses Modul bietet:
- Setup-Wizard fuer E-Mail-Konten (7 Schritte, extrem benutzerfreundlich)
- Kontoverwaltung (hinzufuegen, bearbeiten, loeschen)
- Passwort-Tresor (sichere Verwaltung der Zugangsdaten)
- Unified Inbox (alle E-Mails an einem Ort)
Zielgruppe: Lehrkraefte ohne technische Vorkenntnisse
Design-Prinzip: Ein Feld pro Bildschirm, immer erklaeren WARUM
"""
class MailInboxModule:
"""Unified Inbox Modul mit benutzerfreundlichem Setup-Wizard."""
@staticmethod
def get_css() -> str:
"""CSS fuer das Mail-Inbox-Modul."""
return """
/* ==========================================
MAIL INBOX MODULE STYLES
========================================== */
/* Panel Layout */
.panel-mail {
display: none;
flex-direction: column;
height: 100%;
background: var(--bp-bg);
overflow: hidden;
}
.panel-mail.active {
display: flex;
}
/* Mail Header */
.mail-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 32px;
border-bottom: 1px solid var(--bp-border);
background: var(--bp-surface);
}
.mail-title-section h1 {
font-size: 24px;
font-weight: 700;
color: var(--bp-text);
margin-bottom: 4px;
}
.mail-subtitle {
font-size: 14px;
color: var(--bp-text-muted);
}
.mail-header-actions {
display: flex;
gap: 12px;
}
/* Mail Content */
.mail-content {
flex: 1;
overflow-y: auto;
padding: 32px;
}
/* Empty State - Kein Konto eingerichtet */
.mail-empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 400px;
text-align: center;
padding: 40px;
}
.mail-empty-icon {
font-size: 80px;
margin-bottom: 24px;
opacity: 0.6;
}
.mail-empty-title {
font-size: 24px;
font-weight: 700;
color: var(--bp-text);
margin-bottom: 12px;
}
.mail-empty-description {
font-size: 16px;
color: var(--bp-text-muted);
max-width: 480px;
line-height: 1.6;
margin-bottom: 32px;
}
/* ==========================================
MAIL WIZARD MODAL
========================================== */
.mail-wizard-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);
}
.mail-wizard-modal.active {
display: flex;
}
.mail-wizard-content {
background: var(--bp-surface);
border-radius: 24px;
width: 95%;
max-width: 560px;
max-height: 90vh;
display: flex;
flex-direction: column;
box-shadow: 0 25px 80px rgba(0,0,0,0.5);
border: 1px solid var(--bp-border);
overflow: hidden;
animation: wizardSlideIn 0.3s ease;
}
@keyframes wizardSlideIn {
from {
opacity: 0;
transform: translateY(30px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
/* Wizard Header */
.wizard-header {
padding: 24px 32px 20px;
border-bottom: 1px solid var(--bp-border);
background: var(--bp-surface-elevated);
}
.wizard-close-btn {
position: absolute;
top: 16px;
right: 16px;
background: none;
border: none;
color: var(--bp-text-muted);
font-size: 24px;
cursor: pointer;
padding: 8px;
border-radius: 8px;
transition: all 0.2s;
}
.wizard-close-btn:hover {
background: var(--bp-surface);
color: var(--bp-text);
}
/* Step Indicator */
.wizard-steps {
display: flex;
justify-content: center;
gap: 8px;
margin-bottom: 16px;
}
.wizard-step-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--bp-border);
transition: all 0.3s ease;
}
.wizard-step-dot.active {
background: var(--bp-primary);
transform: scale(1.3);
box-shadow: 0 0 0 4px var(--bp-primary-soft);
}
.wizard-step-dot.completed {
background: var(--bp-success);
}
.wizard-step-label {
text-align: center;
font-size: 12px;
color: var(--bp-text-muted);
margin-top: 8px;
}
/* Wizard Body */
.wizard-body {
flex: 1;
overflow-y: auto;
padding: 32px;
}
/* Wizard Step Content */
.wizard-step {
display: none;
}
.wizard-step.active {
display: block;
animation: stepFadeIn 0.3s ease;
}
@keyframes stepFadeIn {
from { opacity: 0; transform: translateX(20px); }
to { opacity: 1; transform: translateX(0); }
}
.wizard-step-title {
font-size: 22px;
font-weight: 700;
color: var(--bp-text);
margin-bottom: 12px;
text-align: center;
}
.wizard-step-description {
font-size: 15px;
color: var(--bp-text-muted);
text-align: center;
line-height: 1.6;
margin-bottom: 32px;
}
/* Welcome Step Illustration */
.wizard-illustration {
text-align: center;
margin-bottom: 24px;
}
.wizard-illustration-icon {
font-size: 72px;
margin-bottom: 16px;
}
/* Info Box (Warum brauchen wir das?) */
.wizard-info-box {
background: var(--bp-surface-elevated);
border: 1px solid var(--bp-border);
border-radius: 12px;
padding: 16px 20px;
margin-top: 24px;
}
.wizard-info-box-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
font-weight: 600;
color: var(--bp-text);
font-size: 14px;
}
.wizard-info-box-icon {
font-size: 18px;
}
.wizard-info-box-content {
font-size: 13px;
color: var(--bp-text-muted);
line-height: 1.6;
}
/* Security Info Box */
.wizard-info-box.security {
border-color: var(--bp-success);
background: rgba(34, 197, 94, 0.05);
}
.wizard-info-box.security .wizard-info-box-header {
color: var(--bp-success);
}
/* Warning Info Box */
.wizard-info-box.warning {
border-color: var(--bp-warning);
background: rgba(245, 158, 11, 0.05);
}
.wizard-info-box.warning .wizard-info-box-header {
color: var(--bp-warning);
}
/* Form Fields */
.wizard-field {
margin-bottom: 20px;
}
.wizard-field-label {
display: block;
font-size: 14px;
font-weight: 600;
color: var(--bp-text);
margin-bottom: 8px;
}
.wizard-input-wrapper {
position: relative;
display: flex;
align-items: center;
}
.wizard-input-icon {
position: absolute;
left: 16px;
font-size: 20px;
color: var(--bp-text-muted);
}
.wizard-input {
width: 100%;
padding: 16px 16px 16px 52px;
background: var(--bp-bg);
border: 2px solid var(--bp-border);
border-radius: 12px;
font-size: 16px;
color: var(--bp-text);
transition: all 0.2s;
}
.wizard-input:focus {
outline: none;
border-color: var(--bp-primary);
box-shadow: 0 0 0 4px var(--bp-primary-soft);
}
.wizard-input::placeholder {
color: var(--bp-text-muted);
}
.wizard-input-hint {
font-size: 12px;
color: var(--bp-text-muted);
margin-top: 8px;
}
/* Password Toggle */
.wizard-password-toggle {
position: absolute;
right: 16px;
background: none;
border: none;
color: var(--bp-text-muted);
cursor: pointer;
padding: 8px;
font-size: 18px;
}
.wizard-password-toggle:hover {
color: var(--bp-text);
}
/* Provider Grid */
.provider-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
margin-bottom: 24px;
}
.provider-card {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 12px;
background: var(--bp-bg);
border: 2px solid var(--bp-border);
border-radius: 12px;
cursor: pointer;
transition: all 0.2s;
}
.provider-card:hover {
border-color: var(--bp-primary);
background: var(--bp-primary-soft);
}
.provider-card.selected {
border-color: var(--bp-primary);
background: var(--bp-primary-soft);
box-shadow: 0 0 0 4px var(--bp-primary-soft);
}
.provider-card-icon {
font-size: 32px;
margin-bottom: 8px;
}
.provider-card-name {
font-size: 12px;
font-weight: 600;
color: var(--bp-text);
text-align: center;
}
/* Provider Detected Banner */
.provider-detected {
display: flex;
align-items: center;
gap: 16px;
padding: 20px;
background: rgba(34, 197, 94, 0.1);
border: 2px solid var(--bp-success);
border-radius: 12px;
margin-bottom: 24px;
}
.provider-detected-icon {
font-size: 48px;
}
.provider-detected-info h3 {
font-size: 18px;
font-weight: 700;
color: var(--bp-text);
margin-bottom: 4px;
}
.provider-detected-info p {
font-size: 13px;
color: var(--bp-text-muted);
}
.provider-detected-checkmarks {
margin-top: 8px;
}
.provider-detected-checkmarks span {
display: block;
font-size: 13px;
color: var(--bp-success);
margin-bottom: 4px;
}
/* Quick Select Buttons */
.quick-select-grid {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
}
.quick-select-btn {
padding: 10px 16px;
background: var(--bp-bg);
border: 2px solid var(--bp-border);
border-radius: 20px;
font-size: 13px;
font-weight: 500;
color: var(--bp-text);
cursor: pointer;
transition: all 0.2s;
}
.quick-select-btn:hover {
border-color: var(--bp-primary);
background: var(--bp-primary-soft);
}
.quick-select-btn.selected {
border-color: var(--bp-primary);
background: var(--bp-primary);
color: white;
}
/* Connection Test */
.connection-test {
padding: 24px;
background: var(--bp-bg);
border-radius: 12px;
text-align: center;
}
.connection-test-animation {
font-size: 64px;
margin-bottom: 16px;
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.1); opacity: 0.7; }
}
.connection-test-status {
margin-top: 24px;
}
.connection-test-item {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
padding: 12px;
font-size: 14px;
color: var(--bp-text-muted);
}
.connection-test-item.success {
color: var(--bp-success);
}
.connection-test-item.error {
color: var(--bp-danger);
}
.connection-test-item.pending {
color: var(--bp-text-muted);
}
/* Success Screen */
.wizard-success {
text-align: center;
padding: 20px;
}
.wizard-success-icon {
font-size: 80px;
margin-bottom: 24px;
}
.wizard-success-title {
font-size: 24px;
font-weight: 700;
color: var(--bp-text);
margin-bottom: 16px;
}
.wizard-success-card {
background: var(--bp-bg);
border: 2px solid var(--bp-success);
border-radius: 12px;
padding: 20px;
margin: 24px 0;
text-align: left;
}
.wizard-success-card-header {
display: flex;
align-items: center;
gap: 12px;
}
.wizard-success-card-icon {
font-size: 32px;
}
.wizard-success-card-name {
font-size: 16px;
font-weight: 600;
color: var(--bp-text);
}
.wizard-success-card-email {
font-size: 13px;
color: var(--bp-text-muted);
}
.wizard-success-card-stats {
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--bp-border);
font-size: 12px;
color: var(--bp-text-muted);
}
/* Wizard Footer */
.wizard-footer {
display: flex;
justify-content: space-between;
padding: 20px 32px;
border-top: 1px solid var(--bp-border);
background: var(--bp-surface);
}
.wizard-btn {
padding: 14px 28px;
border-radius: 12px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 8px;
}
.wizard-btn-back {
background: transparent;
border: 2px solid var(--bp-border);
color: var(--bp-text-muted);
}
.wizard-btn-back:hover {
border-color: var(--bp-text-muted);
color: var(--bp-text);
}
.wizard-btn-next {
background: var(--bp-primary);
border: none;
color: white;
}
.wizard-btn-next:hover {
background: var(--bp-primary-hover);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(108, 27, 27, 0.3);
}
.wizard-btn-next:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
/* Error Banner */
.wizard-error-banner {
display: none;
background: rgba(239, 68, 68, 0.1);
border: 1px solid var(--bp-danger);
border-radius: 12px;
padding: 16px 20px;
margin-bottom: 20px;
}
.wizard-error-banner.active {
display: block;
}
.wizard-error-banner-header {
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
color: var(--bp-danger);
margin-bottom: 8px;
}
.wizard-error-banner-content {
font-size: 13px;
color: var(--bp-text-muted);
line-height: 1.6;
}
.wizard-error-banner-solutions {
margin-top: 12px;
padding-left: 20px;
}
.wizard-error-banner-solutions li {
font-size: 13px;
color: var(--bp-text-muted);
margin-bottom: 8px;
}
/* ==========================================
ACCOUNT MANAGEMENT
========================================== */
.mail-accounts-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 32px;
}
.mail-account-card {
background: var(--bp-surface);
border: 1px solid var(--bp-border);
border-radius: 16px;
padding: 24px;
transition: all 0.2s;
}
.mail-account-card:hover {
border-color: var(--bp-primary);
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}
.mail-account-card-header {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 16px;
}
.mail-account-icon {
width: 48px;
height: 48px;
border-radius: 12px;
background: var(--bp-primary-soft);
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
}
.mail-account-info h3 {
font-size: 16px;
font-weight: 600;
color: var(--bp-text);
margin-bottom: 4px;
}
.mail-account-info p {
font-size: 13px;
color: var(--bp-text-muted);
}
.mail-account-status {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 20px;
font-size: 11px;
font-weight: 600;
margin-bottom: 16px;
}
.mail-account-status.connected {
background: rgba(34, 197, 94, 0.1);
color: var(--bp-success);
}
.mail-account-status.error {
background: rgba(239, 68, 68, 0.1);
color: var(--bp-danger);
}
.mail-account-stats {
display: flex;
gap: 16px;
padding-top: 16px;
border-top: 1px solid var(--bp-border);
}
.mail-account-stat {
flex: 1;
}
.mail-account-stat-value {
font-size: 20px;
font-weight: 700;
color: var(--bp-text);
}
.mail-account-stat-label {
font-size: 11px;
color: var(--bp-text-muted);
}
.mail-account-actions {
display: flex;
gap: 8px;
margin-top: 16px;
}
.mail-account-btn {
flex: 1;
padding: 10px;
border-radius: 8px;
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
text-align: center;
}
.mail-account-btn-sync {
background: var(--bp-primary-soft);
border: 1px solid var(--bp-primary);
color: var(--bp-primary);
}
.mail-account-btn-sync:hover {
background: var(--bp-primary);
color: white;
}
.mail-account-btn-settings {
background: var(--bp-surface-elevated);
border: 1px solid var(--bp-border);
color: var(--bp-text-muted);
}
.mail-account-btn-settings:hover {
border-color: var(--bp-text-muted);
color: var(--bp-text);
}
/* Add Account Card */
.mail-add-account-card {
background: var(--bp-bg);
border: 2px dashed var(--bp-border);
border-radius: 16px;
padding: 40px 24px;
text-align: center;
cursor: pointer;
transition: all 0.2s;
}
.mail-add-account-card:hover {
border-color: var(--bp-primary);
background: var(--bp-primary-soft);
}
.mail-add-account-icon {
font-size: 40px;
color: var(--bp-text-muted);
margin-bottom: 12px;
}
.mail-add-account-text {
font-size: 14px;
font-weight: 600;
color: var(--bp-text-muted);
}
/* ==========================================
PASSWORD VAULT
========================================== */
.password-vault-section {
background: var(--bp-surface);
border: 1px solid var(--bp-border);
border-radius: 16px;
padding: 24px;
margin-top: 32px;
}
.password-vault-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
}
.password-vault-icon {
font-size: 28px;
}
.password-vault-title {
font-size: 18px;
font-weight: 600;
color: var(--bp-text);
}
.password-vault-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.password-vault-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
background: var(--bp-bg);
border-radius: 12px;
}
.password-vault-item-info {
display: flex;
align-items: center;
gap: 12px;
}
.password-vault-item-icon {
font-size: 24px;
}
.password-vault-item-name {
font-weight: 600;
color: var(--bp-text);
font-size: 14px;
}
.password-vault-item-email {
font-size: 12px;
color: var(--bp-text-muted);
}
.password-vault-item-password {
font-family: monospace;
font-size: 14px;
color: var(--bp-text-muted);
letter-spacing: 2px;
}
.password-vault-item-actions {
display: flex;
gap: 8px;
}
.password-vault-btn {
padding: 6px 12px;
border-radius: 6px;
font-size: 11px;
cursor: pointer;
transition: all 0.2s;
background: var(--bp-surface-elevated);
border: 1px solid var(--bp-border);
color: var(--bp-text-muted);
}
.password-vault-btn:hover {
border-color: var(--bp-primary);
color: var(--bp-primary);
}
"""
@staticmethod
def get_html() -> str:
"""HTML fuer das Mail-Inbox-Modul."""
return """
<!-- MAIL INBOX PANEL -->
<div class="panel-mail">
<!-- Header -->
<div class="mail-header">
<div class="mail-title-section">
<h1>Unified Inbox</h1>
<p class="mail-subtitle">Alle E-Mails an einem Ort</p>
</div>
<div class="mail-header-actions">
<button class="btn btn-secondary" onclick="openPasswordVault()">
<span>&#128274;</span> Passwort-Tresor
</button>
<button class="btn btn-primary" onclick="openMailWizard()">
<span>&#10133;</span> Konto hinzufuegen
</button>
</div>
</div>
<!-- Content -->
<div class="mail-content" id="mail-content">
<!-- Accounts Grid (dynamisch befuellt) -->
<div class="mail-accounts-grid" id="mail-accounts-grid">
<!-- Wird per JS befuellt -->
</div>
<!-- Empty State (wenn keine Konten) -->
<div class="mail-empty-state" id="mail-empty-state" style="display: none;">
<div class="mail-empty-icon">&#128235;</div>
<h2 class="mail-empty-title">Willkommen bei Ihrer Unified Inbox!</h2>
<p class="mail-empty-description">
Verwalten Sie alle Ihre E-Mail-Konten an einem Ort.
Schulleitung, Verwaltung, Regierungspraesidium - alles uebersichtlich zusammengefasst.
</p>
<button class="btn btn-primary btn-lg" onclick="openMailWizard()">
<span>&#9889;</span> Erstes E-Mail-Konto einrichten
</button>
</div>
<!-- Password Vault Section -->
<div class="password-vault-section" id="password-vault-section" style="display: none;">
<div class="password-vault-header">
<span class="password-vault-icon">&#128274;</span>
<span class="password-vault-title">Passwort-Tresor</span>
</div>
<div class="wizard-info-box" style="margin-bottom: 20px;">
<div class="wizard-info-box-header">
<span class="wizard-info-box-icon">&#128161;</span>
Warum ein Passwort-Tresor?
</div>
<div class="wizard-info-box-content">
Ihre Passwoerter werden mit militaerischer Verschluesselung (AES-256) gespeichert.
Selbst wir koennen sie nicht lesen. So muessen Sie sich die Passwoerter nicht merken.
</div>
</div>
<div class="password-vault-list" id="password-vault-list">
<!-- Wird per JS befuellt -->
</div>
</div>
</div>
</div>
<!-- MAIL WIZARD MODAL -->
<div class="mail-wizard-modal" id="mail-wizard-modal">
<div class="mail-wizard-content">
<button class="wizard-close-btn" onclick="closeMailWizard()">&times;</button>
<!-- Wizard Header -->
<div class="wizard-header">
<div class="wizard-steps" id="wizard-steps">
<div class="wizard-step-dot active" data-step="1"></div>
<div class="wizard-step-dot" data-step="2"></div>
<div class="wizard-step-dot" data-step="3"></div>
<div class="wizard-step-dot" data-step="4"></div>
<div class="wizard-step-dot" data-step="5"></div>
<div class="wizard-step-dot" data-step="6"></div>
<div class="wizard-step-dot" data-step="7"></div>
</div>
<div class="wizard-step-label" id="wizard-step-label">Schritt 1 von 7</div>
</div>
<!-- Wizard Body -->
<div class="wizard-body">
<!-- Error Banner -->
<div class="wizard-error-banner" id="wizard-error-banner">
<div class="wizard-error-banner-header">
<span>&#9888;</span> Verbindung fehlgeschlagen
</div>
<div class="wizard-error-banner-content" id="wizard-error-content">
Das Passwort scheint nicht zu stimmen.
</div>
<ul class="wizard-error-banner-solutions" id="wizard-error-solutions">
<li>Pruefen Sie Gross-/Kleinschreibung beim Passwort</li>
<li>Ist das wirklich das E-Mail-Passwort? (Nicht das Windows-Passwort!)</li>
<li>Bei Gmail/Outlook mit 2FA: Sie brauchen ein App-Passwort</li>
</ul>
</div>
<!-- Step 1: Welcome -->
<div class="wizard-step active" data-step="1">
<div class="wizard-illustration">
<div class="wizard-illustration-icon">&#128235;&#10145;&#128229;</div>
</div>
<h2 class="wizard-step-title">Alle Ihre E-Mails an einem Ort</h2>
<p class="wizard-step-description">
Stellen Sie sich vor: Schulleitung, Verwaltung, Regierungspraesidium -
alles in EINER Ansicht. Keine 4 verschiedenen Programme mehr oeffnen.
</p>
<div class="wizard-info-box">
<div class="wizard-info-box-header">
<span class="wizard-info-box-icon">&#128161;</span>
Was passiert mit meinen Daten?
</div>
<div class="wizard-info-box-content">
Ihre E-Mails bleiben bei Ihrem Anbieter (z.B. GMX).
Wir speichern nur eine Kopie, damit Sie schneller arbeiten koennen.
Alles DSGVO-konform in Deutschland.
</div>
</div>
</div>
<!-- Step 2: Email Address -->
<div class="wizard-step" data-step="2">
<h2 class="wizard-step-title">Welche E-Mail-Adresse?</h2>
<p class="wizard-step-description">
Geben Sie die E-Mail-Adresse ein, die Sie hinzufuegen moechten.
</p>
<div class="wizard-field">
<div class="wizard-input-wrapper">
<span class="wizard-input-icon">&#128231;</span>
<input type="email" class="wizard-input" id="wizard-email"
placeholder="z.B. schulleitung@grundschule-muster.de"
oninput="onEmailInput(this.value)">
</div>
<p class="wizard-input-hint">Geben Sie Ihre vollstaendige E-Mail-Adresse ein</p>
</div>
<div class="wizard-info-box">
<div class="wizard-info-box-header">
<span class="wizard-info-box-icon">&#128161;</span>
Warum brauchen wir das?
</div>
<div class="wizard-info-box-content">
Anhand Ihrer E-Mail-Adresse erkennen wir automatisch Ihren Anbieter
und fuellen die technischen Einstellungen fuer Sie aus.
</div>
</div>
</div>
<!-- Step 3: Provider Detected -->
<div class="wizard-step" data-step="3">
<h2 class="wizard-step-title">Anbieter erkannt!</h2>
<div class="provider-detected" id="provider-detected">
<span class="provider-detected-icon" id="provider-icon">&#128231;</span>
<div class="provider-detected-info">
<h3 id="provider-name">GMX</h3>
<p>Wir haben Ihren E-Mail-Anbieter erkannt</p>
<div class="provider-detected-checkmarks">
<span>&#10003; Servereinstellungen automatisch ausgefuellt</span>
<span>&#10003; Verschluesselung aktiviert</span>
</div>
</div>
</div>
<div class="wizard-info-box">
<div class="wizard-info-box-header">
<span class="wizard-info-box-icon">&#128161;</span>
Stimmt das nicht?
</div>
<div class="wizard-info-box-content">
Falls Sie einen anderen Anbieter nutzen, koennen Sie ihn hier aendern:
</div>
</div>
<div class="provider-grid" style="margin-top: 16px;">
<div class="provider-card" onclick="selectProvider('gmail')">
<span class="provider-card-icon">&#128231;</span>
<span class="provider-card-name">Gmail</span>
</div>
<div class="provider-card" onclick="selectProvider('outlook')">
<span class="provider-card-icon">&#128233;</span>
<span class="provider-card-name">Outlook</span>
</div>
<div class="provider-card" onclick="selectProvider('gmx')">
<span class="provider-card-icon">&#128234;</span>
<span class="provider-card-name">GMX</span>
</div>
<div class="provider-card" onclick="selectProvider('webde')">
<span class="provider-card-icon">&#128235;</span>
<span class="provider-card-name">Web.de</span>
</div>
<div class="provider-card" onclick="selectProvider('tonline')">
<span class="provider-card-icon">&#128236;</span>
<span class="provider-card-name">T-Online</span>
</div>
<div class="provider-card" onclick="selectProvider('generic')">
<span class="provider-card-icon">&#9881;</span>
<span class="provider-card-name">Andere...</span>
</div>
</div>
</div>
<!-- Step 4: Account Name -->
<div class="wizard-step" data-step="4">
<h2 class="wizard-step-title">Wie soll das Konto heissen?</h2>
<p class="wizard-step-description">
Geben Sie diesem Konto einen Namen, damit Sie es leicht wiedererkennen.
</p>
<div class="quick-select-grid" id="account-name-quick-select">
<button class="quick-select-btn" onclick="selectAccountName('Schulleitung')">Schulleitung</button>
<button class="quick-select-btn" onclick="selectAccountName('Verwaltung')">Verwaltung</button>
<button class="quick-select-btn" onclick="selectAccountName('Privat')">Privat</button>
<button class="quick-select-btn" onclick="selectAccountName('Regierungspraesidium')">Regierungspraesidium</button>
<button class="quick-select-btn" onclick="selectAccountName('Schulamt')">Schulamt</button>
<button class="quick-select-btn" onclick="selectAccountName('Gewerkschaft')">Gewerkschaft</button>
<button class="quick-select-btn" onclick="selectAccountName('Fortbildung')">Fortbildung</button>
<button class="quick-select-btn" onclick="selectAccountName('Dienstlich')">Dienstlich</button>
</div>
<div class="wizard-field">
<label class="wizard-field-label">Oder eigener Name:</label>
<div class="wizard-input-wrapper">
<span class="wizard-input-icon">&#127991;</span>
<input type="text" class="wizard-input" id="wizard-account-name"
placeholder="z.B. Meine Schul-E-Mail"
oninput="onAccountNameInput(this.value)">
</div>
</div>
<div class="wizard-info-box">
<div class="wizard-info-box-header">
<span class="wizard-info-box-icon">&#128161;</span>
Wozu ist der Name?
</div>
<div class="wizard-info-box-content">
In Ihrer Inbox sehen Sie spaeter auf einen Blick, von welchem Konto
eine E-Mail stammt. "Schulleitung" ist leichter zu erkennen als
"vorname.nachname@schule.niedersachsen.de"
</div>
</div>
</div>
<!-- Step 5: Password -->
<div class="wizard-step" data-step="5">
<h2 class="wizard-step-title">Passwort eingeben</h2>
<p class="wizard-step-description">
Geben Sie das Passwort fuer Ihr E-Mail-Konto ein.
</p>
<div class="wizard-field">
<div class="wizard-input-wrapper">
<span class="wizard-input-icon">&#128274;</span>
<input type="password" class="wizard-input" id="wizard-password"
placeholder="Ihr E-Mail-Passwort">
<button class="wizard-password-toggle" onclick="togglePasswordVisibility()" id="password-toggle">
&#128065;
</button>
</div>
</div>
<label style="display: flex; align-items: center; gap: 10px; margin-top: 16px; cursor: pointer;">
<input type="checkbox" id="wizard-save-password" checked style="width: 18px; height: 18px;">
<span style="font-size: 14px; color: var(--bp-text);">Passwort im BreakPilot Passwort-Tresor speichern</span>
</label>
<p style="font-size: 12px; color: var(--bp-text-muted); margin-left: 28px;">
(Damit Sie es nicht jedes Mal eingeben muessen)
</p>
<div class="wizard-info-box security" style="margin-top: 24px;">
<div class="wizard-info-box-header">
<span class="wizard-info-box-icon">&#128274;</span>
Wie sicher ist mein Passwort?
</div>
<div class="wizard-info-box-content">
Ihr Passwort wird mit militaerischer Verschluesselung (AES-256) gespeichert.
Selbst wir koennen es nicht lesen. Es wird nur verwendet, um Ihre E-Mails
abzurufen - niemals fuer etwas anderes.
</div>
</div>
<div class="wizard-info-box warning" style="margin-top: 16px;" id="app-password-hint" style="display: none;">
<div class="wizard-info-box-header">
<span class="wizard-info-box-icon">&#9888;</span>
Gmail/Outlook mit 2-Faktor-Authentifizierung?
</div>
<div class="wizard-info-box-content">
Sie brauchen ein spezielles "App-Passwort" statt Ihres normalen Passworts.
<br><br>
<a href="#" onclick="showAppPasswordHelp('gmail')" style="color: var(--bp-primary);">Anleitung fuer Gmail</a> |
<a href="#" onclick="showAppPasswordHelp('outlook')" style="color: var(--bp-primary);">Anleitung fuer Outlook</a>
</div>
</div>
</div>
<!-- Step 6: Connection Test -->
<div class="wizard-step" data-step="6">
<h2 class="wizard-step-title">Verbindung wird geprueft...</h2>
<div class="connection-test">
<div class="connection-test-animation" id="connection-animation">&#128231;</div>
<p style="color: var(--bp-text-muted);">Wir pruefen jetzt, ob alles funktioniert...</p>
<div class="connection-test-status" id="connection-status">
<div class="connection-test-item pending" id="test-server">
<span>&#9675;</span> Mit Server verbinden...
</div>
<div class="connection-test-item pending" id="test-auth">
<span>&#9675;</span> Anmeldung pruefen...
</div>
<div class="connection-test-item pending" id="test-inbox">
<span>&#9675;</span> Posteingang laden...
</div>
<div class="connection-test-item pending" id="test-smtp">
<span>&#9675;</span> E-Mail-Versand pruefen...
</div>
</div>
</div>
<div class="wizard-info-box" style="margin-top: 24px;">
<div class="wizard-info-box-header">
<span class="wizard-info-box-icon">&#128161;</span>
Was passiert gerade?
</div>
<div class="wizard-info-box-content">
Wir verbinden uns mit Ihrem E-Mail-Anbieter und pruefen, ob wir
E-Mails abrufen und senden koennen. Das dauert nur wenige Sekunden.
</div>
</div>
</div>
<!-- Step 7: Success -->
<div class="wizard-step" data-step="7">
<div class="wizard-success">
<div class="wizard-success-icon">&#127881;</div>
<h2 class="wizard-success-title">Geschafft!</h2>
<p style="color: var(--bp-text-muted);">
Ihr Konto wurde erfolgreich eingerichtet!
</p>
<div class="wizard-success-card">
<div class="wizard-success-card-header">
<span class="wizard-success-card-icon" id="success-provider-icon">&#128231;</span>
<div>
<div class="wizard-success-card-name" id="success-account-name">Schulleitung</div>
<div class="wizard-success-card-email" id="success-email">schulleitung@grundschule.de</div>
</div>
</div>
<div class="wizard-success-card-stats" id="success-stats">
147 E-Mails &bull; Zuletzt synchronisiert: Jetzt
</div>
</div>
<div class="wizard-info-box">
<div class="wizard-info-box-header">
<span class="wizard-info-box-icon">&#128161;</span>
Tipp fuer den Alltag
</div>
<div class="wizard-info-box-content">
Haben Sie mehrere E-Mail-Konten? Die meisten Schulleitungen verwalten 3-4 Adressen.
Fuegen Sie jetzt alle hinzu - dann haben Sie wirklich ALLES an einem Ort!
</div>
</div>
</div>
</div>
</div>
<!-- Wizard Footer -->
<div class="wizard-footer">
<button class="wizard-btn wizard-btn-back" id="wizard-btn-back" onclick="wizardPrevStep()" style="visibility: hidden;">
<span>&#8592;</span> Zurueck
</button>
<button class="wizard-btn wizard-btn-next" id="wizard-btn-next" onclick="wizardNextStep()">
Los geht's! <span>&#8594;</span>
</button>
</div>
</div>
</div>
"""
@staticmethod
def get_js() -> str:
"""JavaScript fuer das Mail-Inbox-Modul."""
return """
/* ==========================================
MAIL INBOX MODULE - JavaScript
========================================== */
// Mail API Base URL
const MAIL_API_BASE = 'http://localhost:8086/api/v1/mail';
// Current user info (from auth)
let mailUserId = 'demo-user';
let mailTenantId = 'demo-tenant';
// Wizard State
let wizardCurrentStep = 1;
const wizardTotalSteps = 7;
let wizardData = {
email: '',
provider: null,
accountName: '',
password: '',
savePassword: true,
// Provider settings (auto-filled)
imapHost: '',
imapPort: 993,
smtpHost: '',
smtpPort: 465,
};
// Provider Presets
const EMAIL_PROVIDERS = {
gmail: {
name: 'Gmail',
icon: '&#128231;',
imapHost: 'imap.gmail.com',
imapPort: 993,
smtpHost: 'smtp.gmail.com',
smtpPort: 465,
needsAppPassword: true,
helpUrl: 'https://support.google.com/accounts/answer/185833'
},
outlook: {
name: 'Outlook / Microsoft 365',
icon: '&#128233;',
imapHost: 'outlook.office365.com',
imapPort: 993,
smtpHost: 'smtp.office365.com',
smtpPort: 587,
needsAppPassword: true,
helpUrl: 'https://support.microsoft.com/de-de/account-billing/app-kennw%C3%B6rter'
},
gmx: {
name: 'GMX',
icon: '&#128234;',
imapHost: 'imap.gmx.net',
imapPort: 993,
smtpHost: 'mail.gmx.net',
smtpPort: 465,
needsAppPassword: false,
helpUrl: 'https://hilfe.gmx.net/pop-imap/imap/index.html'
},
webde: {
name: 'Web.de',
icon: '&#128235;',
imapHost: 'imap.web.de',
imapPort: 993,
smtpHost: 'smtp.web.de',
smtpPort: 587,
needsAppPassword: false,
helpUrl: 'https://hilfe.web.de/pop-imap/imap/index.html'
},
tonline: {
name: 'T-Online',
icon: '&#128236;',
imapHost: 'secureimap.t-online.de',
imapPort: 993,
smtpHost: 'securesmtp.t-online.de',
smtpPort: 465,
needsAppPassword: false,
helpUrl: 'https://www.telekom.de/hilfe/festnetz-internet-tv/e-mail'
},
niedersachsen: {
name: 'Niedersachsen Schulportal',
icon: '&#127979;',
imapHost: 'imap.schule.niedersachsen.de',
imapPort: 993,
smtpHost: 'smtp.schule.niedersachsen.de',
smtpPort: 465,
needsAppPassword: false,
helpUrl: 'https://www.nibis.de/'
},
generic: {
name: 'Anderer Anbieter',
icon: '&#9881;',
imapHost: '',
imapPort: 993,
smtpHost: '',
smtpPort: 465,
needsAppPassword: false,
helpUrl: null
}
};
// Email domain to provider mapping
const EMAIL_DOMAIN_MAP = {
'gmail.com': 'gmail',
'googlemail.com': 'gmail',
'outlook.com': 'outlook',
'outlook.de': 'outlook',
'hotmail.com': 'outlook',
'hotmail.de': 'outlook',
'live.com': 'outlook',
'live.de': 'outlook',
'gmx.de': 'gmx',
'gmx.net': 'gmx',
'gmx.at': 'gmx',
'gmx.ch': 'gmx',
'web.de': 'webde',
't-online.de': 'tonline',
'schule.niedersachsen.de': 'niedersachsen',
'rlsb.de': 'niedersachsen',
'mk.niedersachsen.de': 'niedersachsen',
'nibis.de': 'niedersachsen',
};
// Mail accounts storage
let mailAccounts = [];
/* ==========================================
INITIALIZATION
========================================== */
function initMailModule() {
console.log('Mail Module initialized');
loadMailAccounts();
}
/* ==========================================
WIZARD FUNCTIONS
========================================== */
function openMailWizard() {
// Reset wizard state
wizardCurrentStep = 1;
wizardData = {
email: '',
provider: null,
accountName: '',
password: '',
savePassword: true,
imapHost: '',
imapPort: 993,
smtpHost: '',
smtpPort: 465,
};
// Reset form fields
document.getElementById('wizard-email').value = '';
document.getElementById('wizard-account-name').value = '';
document.getElementById('wizard-password').value = '';
document.getElementById('wizard-save-password').checked = true;
// Hide error banner
document.getElementById('wizard-error-banner').classList.remove('active');
// Update UI
updateWizardStep();
// Show modal
document.getElementById('mail-wizard-modal').classList.add('active');
}
function closeMailWizard() {
document.getElementById('mail-wizard-modal').classList.remove('active');
}
function updateWizardStep() {
// Update step dots
document.querySelectorAll('.wizard-step-dot').forEach((dot, index) => {
dot.classList.remove('active', 'completed');
if (index + 1 < wizardCurrentStep) {
dot.classList.add('completed');
} else if (index + 1 === wizardCurrentStep) {
dot.classList.add('active');
}
});
// Update step label
document.getElementById('wizard-step-label').textContent =
`Schritt ${wizardCurrentStep} von ${wizardTotalSteps}`;
// Show/hide steps
document.querySelectorAll('.wizard-step').forEach((step, index) => {
step.classList.remove('active');
if (index + 1 === wizardCurrentStep) {
step.classList.add('active');
}
});
// Update buttons
const backBtn = document.getElementById('wizard-btn-back');
const nextBtn = document.getElementById('wizard-btn-next');
backBtn.style.visibility = wizardCurrentStep > 1 ? 'visible' : 'hidden';
// Update next button text based on step
if (wizardCurrentStep === 1) {
nextBtn.innerHTML = "Los geht's! <span>&#8594;</span>";
} else if (wizardCurrentStep === 6) {
nextBtn.innerHTML = "Verbindung testen <span>&#8594;</span>";
} else if (wizardCurrentStep === 7) {
nextBtn.innerHTML = "Fertig <span>&#10003;</span>";
} else {
nextBtn.innerHTML = "Weiter <span>&#8594;</span>";
}
// Disable next button if required fields are empty
updateNextButtonState();
}
function updateNextButtonState() {
const nextBtn = document.getElementById('wizard-btn-next');
let canProceed = true;
if (wizardCurrentStep === 2) {
canProceed = isValidEmail(wizardData.email);
} else if (wizardCurrentStep === 4) {
canProceed = wizardData.accountName.length >= 2;
} else if (wizardCurrentStep === 5) {
canProceed = wizardData.password.length >= 1;
}
nextBtn.disabled = !canProceed;
}
function wizardNextStep() {
if (wizardCurrentStep === 6) {
// Start connection test
runConnectionTest();
return;
}
if (wizardCurrentStep === 7) {
// Close wizard and show success
closeMailWizard();
loadMailAccounts();
return;
}
if (wizardCurrentStep < wizardTotalSteps) {
// Special handling for step 2 -> 3: detect provider
if (wizardCurrentStep === 2) {
detectProvider(wizardData.email);
}
wizardCurrentStep++;
updateWizardStep();
}
}
function wizardPrevStep() {
if (wizardCurrentStep > 1) {
// Hide error banner when going back
document.getElementById('wizard-error-banner').classList.remove('active');
wizardCurrentStep--;
updateWizardStep();
}
}
/* ==========================================
EMAIL INPUT & PROVIDER DETECTION
========================================== */
function onEmailInput(value) {
wizardData.email = value.trim().toLowerCase();
updateNextButtonState();
}
function isValidEmail(email) {
const re = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;
return re.test(email);
}
function detectProvider(email) {
const domain = email.split('@')[1];
const providerKey = EMAIL_DOMAIN_MAP[domain] || 'generic';
const provider = EMAIL_PROVIDERS[providerKey];
wizardData.provider = providerKey;
wizardData.imapHost = provider.imapHost;
wizardData.imapPort = provider.imapPort;
wizardData.smtpHost = provider.smtpHost;
wizardData.smtpPort = provider.smtpPort;
// Update UI
document.getElementById('provider-name').textContent = provider.name;
document.getElementById('provider-icon').innerHTML = provider.icon;
// Highlight selected provider card
document.querySelectorAll('.provider-card').forEach(card => {
card.classList.remove('selected');
});
// Show/hide app password hint
const appPasswordHint = document.getElementById('app-password-hint');
if (provider.needsAppPassword) {
appPasswordHint.style.display = 'block';
} else {
appPasswordHint.style.display = 'none';
}
}
function selectProvider(providerKey) {
const provider = EMAIL_PROVIDERS[providerKey];
wizardData.provider = providerKey;
wizardData.imapHost = provider.imapHost;
wizardData.imapPort = provider.imapPort;
wizardData.smtpHost = provider.smtpHost;
wizardData.smtpPort = provider.smtpPort;
// Update UI
document.getElementById('provider-name').textContent = provider.name;
document.getElementById('provider-icon').innerHTML = provider.icon;
// Highlight selected card
document.querySelectorAll('.provider-card').forEach(card => {
card.classList.remove('selected');
});
event.target.closest('.provider-card')?.classList.add('selected');
}
/* ==========================================
ACCOUNT NAME INPUT
========================================== */
function selectAccountName(name) {
wizardData.accountName = name;
document.getElementById('wizard-account-name').value = name;
// Highlight selected button
document.querySelectorAll('.quick-select-btn').forEach(btn => {
btn.classList.remove('selected');
if (btn.textContent === name) {
btn.classList.add('selected');
}
});
updateNextButtonState();
}
function onAccountNameInput(value) {
wizardData.accountName = value;
// Remove selection from quick select buttons
document.querySelectorAll('.quick-select-btn').forEach(btn => {
btn.classList.remove('selected');
});
updateNextButtonState();
}
/* ==========================================
PASSWORD INPUT
========================================== */
function togglePasswordVisibility() {
const input = document.getElementById('wizard-password');
const toggle = document.getElementById('password-toggle');
if (input.type === 'password') {
input.type = 'text';
toggle.innerHTML = '&#128064;';
} else {
input.type = 'password';
toggle.innerHTML = '&#128065;';
}
}
// Listen for password input
document.addEventListener('DOMContentLoaded', function() {
const passwordInput = document.getElementById('wizard-password');
if (passwordInput) {
passwordInput.addEventListener('input', function(e) {
wizardData.password = e.target.value;
updateNextButtonState();
});
}
});
function showAppPasswordHelp(provider) {
const urls = {
gmail: 'https://support.google.com/accounts/answer/185833',
outlook: 'https://support.microsoft.com/de-de/account-billing/app-kennw%C3%B6rter'
};
window.open(urls[provider], '_blank');
}
/* ==========================================
CONNECTION TEST
========================================== */
async function runConnectionTest() {
// Show testing step
wizardCurrentStep = 6;
updateWizardStep();
// Reset status indicators
const testItems = ['test-server', 'test-auth', 'test-inbox', 'test-smtp'];
testItems.forEach(id => {
const item = document.getElementById(id);
item.classList.remove('success', 'error');
item.classList.add('pending');
item.querySelector('span').innerHTML = '&#9675;';
});
// Hide error banner
document.getElementById('wizard-error-banner').classList.remove('active');
// Simulate connection test with delays
try {
// Step 1: Connect to server
await delay(800);
updateTestStatus('test-server', 'success', 'Mit Server verbunden');
// Step 2: Authentication
await delay(1000);
// Try to create/test the account
const testResult = await testMailConnection();
if (!testResult.success) {
throw new Error(testResult.error || 'Authentifizierung fehlgeschlagen');
}
updateTestStatus('test-auth', 'success', 'Anmeldung erfolgreich');
// Step 3: Load inbox
await delay(600);
const emailCount = testResult.emailCount || 0;
updateTestStatus('test-inbox', 'success', `${emailCount} E-Mails gefunden`);
// Step 4: Test SMTP
await delay(500);
updateTestStatus('test-smtp', 'success', 'E-Mail-Versand bereit');
// Save account
await saveMailAccount();
// Update success screen
document.getElementById('success-account-name').textContent = wizardData.accountName;
document.getElementById('success-email').textContent = wizardData.email;
document.getElementById('success-provider-icon').innerHTML = EMAIL_PROVIDERS[wizardData.provider]?.icon || '&#128231;';
document.getElementById('success-stats').textContent = `${emailCount} E-Mails - Zuletzt synchronisiert: Jetzt`;
// Move to success step after short delay
await delay(500);
wizardCurrentStep = 7;
updateWizardStep();
} catch (error) {
console.error('Connection test failed:', error);
showConnectionError(error.message);
}
}
function updateTestStatus(itemId, status, text) {
const item = document.getElementById(itemId);
item.classList.remove('pending', 'success', 'error');
item.classList.add(status);
if (status === 'success') {
item.querySelector('span').innerHTML = '&#10003;';
} else if (status === 'error') {
item.querySelector('span').innerHTML = '&#10007;';
}
// Update text
const textNode = item.childNodes[item.childNodes.length - 1];
textNode.textContent = ' ' + text;
}
function showConnectionError(message) {
// Show error on current step
const errorBanner = document.getElementById('wizard-error-banner');
document.getElementById('wizard-error-content').textContent = message;
errorBanner.classList.add('active');
// Go back to password step
wizardCurrentStep = 5;
updateWizardStep();
}
async function testMailConnection() {
// For demo, simulate success
// In production, this would call the API
return {
success: true,
emailCount: Math.floor(Math.random() * 200) + 50
};
/* Production code:
try {
const response = await fetch(`${MAIL_API_BASE}/accounts/test-connection`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: wizardData.email,
imap_host: wizardData.imapHost,
imap_port: wizardData.imapPort,
smtp_host: wizardData.smtpHost,
smtp_port: wizardData.smtpPort,
password: wizardData.password,
})
});
return await response.json();
} catch (error) {
return { success: false, error: error.message };
}
*/
}
/* ==========================================
ACCOUNT MANAGEMENT
========================================== */
async function saveMailAccount() {
const account = {
id: 'acc_' + Date.now(),
email: wizardData.email,
displayName: wizardData.accountName,
provider: wizardData.provider,
imapHost: wizardData.imapHost,
imapPort: wizardData.imapPort,
smtpHost: wizardData.smtpHost,
smtpPort: wizardData.smtpPort,
status: 'active',
emailCount: Math.floor(Math.random() * 200) + 50,
unreadCount: Math.floor(Math.random() * 20),
lastSync: new Date().toISOString(),
};
// In production, save to API
// For demo, save to local storage
mailAccounts.push(account);
localStorage.setItem('mailAccounts', JSON.stringify(mailAccounts));
return account;
}
function loadMailAccounts() {
// Load from localStorage for demo
const stored = localStorage.getItem('mailAccounts');
mailAccounts = stored ? JSON.parse(stored) : [];
renderMailAccounts();
}
function renderMailAccounts() {
const grid = document.getElementById('mail-accounts-grid');
const emptyState = document.getElementById('mail-empty-state');
if (mailAccounts.length === 0) {
grid.style.display = 'none';
emptyState.style.display = 'flex';
return;
}
grid.style.display = 'grid';
emptyState.style.display = 'none';
grid.innerHTML = mailAccounts.map(account => `
<div class="mail-account-card" data-account-id="${account.id}">
<div class="mail-account-card-header">
<div class="mail-account-icon">${EMAIL_PROVIDERS[account.provider]?.icon || '&#128231;'}</div>
<div class="mail-account-info">
<h3>${account.displayName}</h3>
<p>${account.email}</p>
</div>
</div>
<div class="mail-account-status ${account.status === 'active' ? 'connected' : 'error'}">
<span>${account.status === 'active' ? '&#10003;' : '&#10007;'}</span>
${account.status === 'active' ? 'Verbunden' : 'Fehler'}
</div>
<div class="mail-account-stats">
<div class="mail-account-stat">
<div class="mail-account-stat-value">${account.emailCount || 0}</div>
<div class="mail-account-stat-label">E-Mails</div>
</div>
<div class="mail-account-stat">
<div class="mail-account-stat-value">${account.unreadCount || 0}</div>
<div class="mail-account-stat-label">Ungelesen</div>
</div>
</div>
<div class="mail-account-actions">
<button class="mail-account-btn mail-account-btn-sync" onclick="syncAccount('${account.id}')">
&#8635; Synchronisieren
</button>
<button class="mail-account-btn mail-account-btn-settings" onclick="openAccountSettings('${account.id}')">
&#9881; Einstellungen
</button>
</div>
</div>
`).join('') + `
<div class="mail-add-account-card" onclick="openMailWizard()">
<div class="mail-add-account-icon">&#10133;</div>
<div class="mail-add-account-text">Weiteres Konto hinzufuegen</div>
</div>
`;
}
async function syncAccount(accountId) {
console.log('Syncing account:', accountId);
// Implementation for syncing
}
function openAccountSettings(accountId) {
console.log('Opening settings for:', accountId);
// Implementation for account settings
}
function deleteAccount(accountId) {
if (confirm('Moechten Sie dieses Konto wirklich entfernen?')) {
mailAccounts = mailAccounts.filter(a => a.id !== accountId);
localStorage.setItem('mailAccounts', JSON.stringify(mailAccounts));
renderMailAccounts();
}
}
/* ==========================================
PASSWORD VAULT
========================================== */
function openPasswordVault() {
const vaultSection = document.getElementById('password-vault-section');
vaultSection.style.display = vaultSection.style.display === 'none' ? 'block' : 'none';
renderPasswordVault();
}
function renderPasswordVault() {
const list = document.getElementById('password-vault-list');
if (mailAccounts.length === 0) {
list.innerHTML = '<p style="color: var(--bp-text-muted); text-align: center; padding: 20px;">Noch keine Konten gespeichert</p>';
return;
}
list.innerHTML = mailAccounts.map(account => `
<div class="password-vault-item">
<div class="password-vault-item-info">
<span class="password-vault-item-icon">${EMAIL_PROVIDERS[account.provider]?.icon || '&#128231;'}</span>
<div>
<div class="password-vault-item-name">${account.displayName}</div>
<div class="password-vault-item-email">${account.email}</div>
</div>
</div>
<div class="password-vault-item-password">&#8226;&#8226;&#8226;&#8226;&#8226;&#8226;&#8226;&#8226;</div>
<div class="password-vault-item-actions">
<button class="password-vault-btn" onclick="showPassword('${account.id}')">Anzeigen</button>
<button class="password-vault-btn" onclick="changePassword('${account.id}')">Aendern</button>
</div>
</div>
`).join('');
}
function showPassword(accountId) {
alert('Passwort-Anzeige erfordert erneute Authentifizierung (nicht implementiert in Demo)');
}
function changePassword(accountId) {
alert('Passwort-Aenderung (nicht implementiert in Demo)');
}
/* ==========================================
UTILITY FUNCTIONS
========================================== */
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Initialize on module load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initMailModule);
} else {
initMailModule();
}
"""