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/components/legal_modal.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

515 lines
22 KiB
Python

"""
Legal Modal Component - AGB, Datenschutz, Cookies, Community Guidelines, GDPR Rights
"""
def get_legal_modal_css() -> str:
"""CSS für Legal Modal zurückgeben"""
return """
/* ==========================================
LEGAL MODAL STYLES
========================================== */
.legal-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);
}
.legal-modal.active {
display: flex;
}
.legal-modal-content {
background: var(--bp-surface);
border-radius: 16px;
width: 90%;
max-width: 700px;
max-height: 80vh;
display: flex;
flex-direction: column;
box-shadow: 0 20px 60px rgba(0,0,0,0.4);
border: 1px solid var(--bp-border);
}
.legal-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 24px;
border-bottom: 1px solid var(--bp-border);
}
.legal-modal-header h2 {
margin: 0;
font-size: 18px;
font-weight: 600;
}
.legal-modal-close {
background: none;
border: none;
font-size: 28px;
cursor: pointer;
color: var(--bp-text-muted);
padding: 0;
line-height: 1;
}
.legal-modal-close:hover {
color: var(--bp-text);
}
.legal-tabs {
display: flex;
gap: 4px;
padding: 12px 24px;
border-bottom: 1px solid var(--bp-border);
background: var(--bp-surface-elevated);
}
.legal-tab {
padding: 8px 16px;
border: none;
background: transparent;
color: var(--bp-text-muted);
cursor: pointer;
border-radius: 8px;
font-size: 13px;
transition: all 0.2s ease;
}
.legal-tab:hover {
background: var(--bp-border-subtle);
color: var(--bp-text);
}
.legal-tab.active {
background: var(--bp-primary);
color: white;
}
.legal-body {
padding: 24px;
overflow-y: auto;
flex: 1;
}
.legal-content {
display: none;
}
.legal-content.active {
display: block;
}
.legal-content h3 {
margin-top: 0;
color: var(--bp-text);
}
.legal-content p {
line-height: 1.6;
color: var(--bp-text-muted);
}
.legal-content ul {
color: var(--bp-text-muted);
line-height: 1.8;
}
.cookie-categories {
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 16px;
}
.cookie-category {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 12px;
background: var(--bp-surface-elevated);
border-radius: 8px;
border: 1px solid var(--bp-border);
cursor: pointer;
}
.cookie-category input {
margin-top: 4px;
}
.cookie-category span {
flex: 1;
font-size: 13px;
color: var(--bp-text);
}
.gdpr-actions {
display: flex;
flex-direction: column;
gap: 16px;
margin-top: 16px;
}
.gdpr-action {
padding: 16px;
background: var(--bp-surface-elevated);
border-radius: 12px;
border: 1px solid var(--bp-border);
}
.gdpr-action h4 {
margin: 0 0 8px 0;
font-size: 14px;
color: var(--bp-text);
}
.gdpr-action p {
margin: 0 0 12px 0;
font-size: 13px;
}
.btn-danger {
background: var(--bp-danger) !important;
border-color: var(--bp-danger) !important;
}
.btn-danger:hover {
filter: brightness(1.1);
}
[data-theme="light"] .legal-modal-content {
background: #FFFFFF;
border-color: #E0E0E0;
}
[data-theme="light"] .legal-tabs {
background: #F5F5F5;
}
[data-theme="light"] .legal-tab.active {
background: var(--bp-primary);
color: white;
}
[data-theme="light"] .cookie-category,
[data-theme="light"] .gdpr-action {
background: #F8F8F8;
border-color: #E0E0E0;
}
"""
def get_legal_modal_html() -> str:
"""HTML für Legal Modal zurückgeben"""
return """
<!-- Legal Modal -->
<div id="legal-modal" class="legal-modal">
<div class="legal-modal-content">
<div class="legal-modal-header">
<h2>Rechtliches</h2>
<button id="legal-modal-close" class="legal-modal-close">&times;</button>
</div>
<div class="legal-tabs">
<button class="legal-tab active" data-tab="terms">AGB</button>
<button class="legal-tab" data-tab="privacy">Datenschutz</button>
<button class="legal-tab" data-tab="community">Community Guidelines</button>
<button class="legal-tab" data-tab="cookies">Cookie-Einstellungen</button>
<button class="legal-tab" data-tab="gdpr">DSGVO-Rechte</button>
</div>
<div class="legal-body">
<div id="legal-terms" class="legal-content active">
<div id="legal-terms-content">
<div class="legal-loading">Lade AGB...</div>
</div>
</div>
<div id="legal-privacy" class="legal-content">
<div id="legal-privacy-content">
<div class="legal-loading">Lade Datenschutzerklärung...</div>
</div>
</div>
<div id="legal-community" class="legal-content">
<div id="legal-community-content">
<div class="legal-loading">Lade Community Guidelines...</div>
</div>
</div>
<div id="legal-cookies" class="legal-content">
<h3>Cookie-Einstellungen</h3>
<p>Wir verwenden Cookies, um Ihnen die bestmögliche Erfahrung zu bieten. Hier können Sie Ihre Präferenzen jederzeit anpassen.</p>
<div id="cookie-categories-container" class="cookie-categories">
<div class="legal-loading">Lade Cookie-Kategorien...</div>
</div>
<button class="btn btn-primary" style="margin-top:16px" onclick="saveCookiePreferences()">Einstellungen speichern</button>
</div>
<div id="legal-gdpr" class="legal-content">
<h3>Ihre DSGVO-Rechte</h3>
<p>Nach der Datenschutz-Grundverordnung haben Sie folgende Rechte:</p>
<div class="gdpr-actions">
<div class="gdpr-action">
<h4>📋 Datenauskunft (Art. 15)</h4>
<p>Erfahren Sie, welche Daten wir über Sie gespeichert haben.</p>
<button class="btn btn-sm" onclick="requestDataExport()">Meine Daten anfordern</button>
</div>
<div class="gdpr-action">
<h4>📤 Datenübertragbarkeit (Art. 20)</h4>
<p>Exportieren Sie Ihre Daten in einem maschinenlesbaren Format.</p>
<button class="btn btn-sm" onclick="requestDataDownload()">Daten exportieren</button>
</div>
<div class="gdpr-action">
<h4>🗑️ Recht auf Löschung (Art. 17)</h4>
<p>Beantragen Sie die vollständige Löschung Ihrer Daten.</p>
<button class="btn btn-sm btn-danger" onclick="requestDataDeletion()">Löschung beantragen</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Imprint Modal (Impressum - muss direkt erreichbar sein) -->
<div id="imprint-modal" class="legal-modal">
<div class="legal-modal-content">
<div class="legal-modal-header">
<h2>Impressum</h2>
<button id="imprint-modal-close" class="legal-modal-close">&times;</button>
</div>
<div class="legal-body" style="padding: 24px;">
<div id="imprint-content">
<div class="legal-loading">Lade Impressum...</div>
</div>
</div>
</div>
</div>
"""
def get_legal_modal_js() -> str:
"""JavaScript für Legal Modal zurückgeben"""
return """
const legalModal = document.getElementById('legal-modal');
const legalModalClose = document.getElementById('legal-modal-close');
const legalTabs = document.querySelectorAll('.legal-tab');
const legalContents = document.querySelectorAll('.legal-content');
const btnLegal = document.getElementById('btn-legal');
// Imprint Modal
const imprintModal = document.getElementById('imprint-modal');
const imprintModalClose = document.getElementById('imprint-modal-close');
// Open legal modal from footer
function openLegalModal(tab = 'terms') {
legalModal.classList.add('active');
// Switch to specified tab
if (tab) {
legalTabs.forEach(t => t.classList.remove('active'));
legalContents.forEach(c => c.classList.remove('active'));
const targetTab = document.querySelector(`.legal-tab[data-tab="${tab}"]`);
if (targetTab) targetTab.classList.add('active');
document.getElementById(`legal-${tab}`)?.classList.add('active');
}
loadLegalDocuments();
}
// Open imprint modal from footer
function openImprintModal() {
imprintModal.classList.add('active');
loadImprintContent();
}
// Open legal modal
btnLegal?.addEventListener('click', async () => {
openLegalModal();
});
// Close legal modal
legalModalClose?.addEventListener('click', () => {
legalModal.classList.remove('active');
});
// Close imprint modal
imprintModalClose?.addEventListener('click', () => {
imprintModal.classList.remove('active');
});
// Close on background click
legalModal?.addEventListener('click', (e) => {
if (e.target === legalModal) {
legalModal.classList.remove('active');
}
});
imprintModal?.addEventListener('click', (e) => {
if (e.target === imprintModal) {
imprintModal.classList.remove('active');
}
});
// Tab switching
legalTabs.forEach(tab => {
tab.addEventListener('click', () => {
const tabId = tab.dataset.tab;
legalTabs.forEach(t => t.classList.remove('active'));
legalContents.forEach(c => c.classList.remove('active'));
tab.classList.add('active');
document.getElementById(`legal-${tabId}`)?.classList.add('active');
// Load cookie categories when switching to cookies tab
if (tabId === 'cookies') {
loadCookieCategories();
}
});
});
// Load legal documents from consent service
async function loadLegalDocuments() {
const lang = document.getElementById('language-select')?.value || 'de';
// Load all documents in parallel
await Promise.all([
loadDocumentContent('terms', 'legal-terms-content', getDefaultTerms, lang),
loadDocumentContent('privacy', 'legal-privacy-content', getDefaultPrivacy, lang),
loadDocumentContent('community_guidelines', 'legal-community-content', getDefaultCommunityGuidelines, lang)
]);
}
// Load imprint content
async function loadImprintContent() {
const lang = document.getElementById('language-select')?.value || 'de';
await loadDocumentContent('imprint', 'imprint-content', getDefaultImprint, lang);
}
// Generic function to load document content
async function loadDocumentContent(docType, containerId, defaultFn, lang) {
const container = document.getElementById(containerId);
if (!container) return;
try {
const res = await fetch(`/api/consent/documents/${docType}/latest?language=${lang}`);
if (res.ok) {
const data = await res.json();
if (data.content) {
container.innerHTML = data.content;
return;
}
}
} catch(e) {
console.log(`Could not load ${docType}:`, e);
}
// Fallback to default
container.innerHTML = defaultFn(lang);
}
// Load cookie categories for the cookie settings tab
async function loadCookieCategories() {
const container = document.getElementById('cookie-categories-container');
if (!container) return;
try {
const res = await fetch('/api/consent/cookies/categories');
if (res.ok) {
const data = await res.json();
const categories = data.categories || [];
if (categories.length === 0) {
container.innerHTML = getDefaultCookieCategories();
return;
}
// Get current preferences from localStorage
const savedPrefs = JSON.parse(localStorage.getItem('bp_cookie_consent') || '{}');
container.innerHTML = categories.map(cat => `
<label class="cookie-category">
<input type="checkbox" id="cookie-${cat.name}"
${cat.is_mandatory ? 'checked disabled' : (savedPrefs[cat.name] ? 'checked' : '')}>
<span>
<strong>${cat.display_name_de || cat.name}</strong>
${cat.is_mandatory ? ' (erforderlich)' : ''}
${cat.description_de ? ` - ${cat.description_de}` : ''}
</span>
</label>
`).join('');
} else {
container.innerHTML = getDefaultCookieCategories();
}
} catch(e) {
container.innerHTML = getDefaultCookieCategories();
}
}
function getDefaultCookieCategories() {
return `
<label class="cookie-category">
<input type="checkbox" checked disabled>
<span><strong>Notwendig</strong> (erforderlich) - Erforderlich für die Grundfunktionen</span>
</label>
<label class="cookie-category">
<input type="checkbox" id="cookie-functional">
<span><strong>Funktional</strong> - Erweiterte Funktionen und Personalisierung</span>
</label>
<label class="cookie-category">
<input type="checkbox" id="cookie-analytics">
<span><strong>Analyse</strong> - Hilft uns, die Nutzung zu verstehen</span>
</label>
`;
}
function getDefaultTerms(lang) {
const terms = {
de: '<h3>Allgemeine Geschäftsbedingungen</h3><p>Die BreakPilot-Plattform wird von der BreakPilot UG bereitgestellt.</p><p><strong>Nutzung:</strong> Die Plattform dient zur Erstellung und Verwaltung von Lernmaterialien für Bildungszwecke.</p><p><strong>Haftung:</strong> Die Nutzung erfolgt auf eigene Verantwortung.</p><p><strong>Änderungen:</strong> Wir behalten uns vor, diese AGB jederzeit zu ändern.</p>',
en: '<h3>Terms of Service</h3><p>The BreakPilot platform is provided by BreakPilot UG.</p><p><strong>Usage:</strong> The platform is designed for creating and managing learning materials for educational purposes.</p><p><strong>Liability:</strong> Use at your own risk.</p><p><strong>Changes:</strong> We reserve the right to modify these terms at any time.</p>'
};
return terms[lang] || terms.de;
}
function getDefaultPrivacy(lang) {
const privacy = {
de: '<h3>Datenschutzerklärung</h3><p><strong>Verantwortlicher:</strong> BreakPilot UG</p><p><strong>Erhobene Daten:</strong> Bei der Nutzung werden technische Daten (IP-Adresse, Browser-Typ) sowie von Ihnen eingegebene Inhalte verarbeitet.</p><p><strong>Zweck:</strong> Die Daten werden zur Bereitstellung der Plattform und zur Verbesserung unserer Dienste genutzt.</p><p><strong>Ihre Rechte (DSGVO):</strong></p><ul><li>Auskunftsrecht (Art. 15)</li><li>Recht auf Berichtigung (Art. 16)</li><li>Recht auf Löschung (Art. 17)</li><li>Recht auf Datenübertragbarkeit (Art. 20)</li></ul><p><strong>Kontakt:</strong> datenschutz@breakpilot.app</p>',
en: '<h3>Privacy Policy</h3><p><strong>Controller:</strong> BreakPilot UG</p><p><strong>Data Collected:</strong> Technical data (IP address, browser type) and content you provide are processed.</p><p><strong>Purpose:</strong> Data is used to provide the platform and improve our services.</p><p><strong>Your Rights (GDPR):</strong></p><ul><li>Right of access (Art. 15)</li><li>Right to rectification (Art. 16)</li><li>Right to erasure (Art. 17)</li><li>Right to data portability (Art. 20)</li></ul><p><strong>Contact:</strong> privacy@breakpilot.app</p>'
};
return privacy[lang] || privacy.de;
}
function getDefaultCommunityGuidelines(lang) {
const guidelines = {
de: '<h3>Community Guidelines</h3><p>Willkommen bei BreakPilot! Um eine positive und respektvolle Umgebung zu gewährleisten, bitten wir alle Nutzer, diese Richtlinien zu befolgen.</p><p><strong>Respektvoller Umgang:</strong> Behandeln Sie andere Nutzer mit Respekt und Höflichkeit.</p><p><strong>Keine illegalen Inhalte:</strong> Das Erstellen oder Teilen von illegalen Inhalten ist streng untersagt.</p><p><strong>Urheberrecht:</strong> Respektieren Sie das geistige Eigentum anderer. Verwenden Sie nur Inhalte, für die Sie die Rechte besitzen.</p><p><strong>Datenschutz:</strong> Teilen Sie keine persönlichen Daten anderer ohne deren ausdrückliche Zustimmung.</p><p><strong>Qualität:</strong> Bemühen Sie sich um qualitativ hochwertige Lerninhalte.</p><p>Verstöße gegen diese Richtlinien können zur Sperrung des Accounts führen.</p>',
en: '<h3>Community Guidelines</h3><p>Welcome to BreakPilot! To ensure a positive and respectful environment, we ask all users to follow these guidelines.</p><p><strong>Respectful Behavior:</strong> Treat other users with respect and courtesy.</p><p><strong>No Illegal Content:</strong> Creating or sharing illegal content is strictly prohibited.</p><p><strong>Copyright:</strong> Respect the intellectual property of others. Only use content you have rights to.</p><p><strong>Privacy:</strong> Do not share personal data of others without their explicit consent.</p><p><strong>Quality:</strong> Strive for high-quality learning content.</p><p>Violations of these guidelines may result in account suspension.</p>'
};
return guidelines[lang] || guidelines.de;
}
function getDefaultImprint(lang) {
const imprint = {
de: '<h3>Impressum</h3><p><strong>Angaben gemäß § 5 TMG:</strong></p><p>BreakPilot UG (haftungsbeschränkt)<br>Musterstraße 1<br>12345 Musterstadt<br>Deutschland</p><p><strong>Vertreten durch:</strong><br>Geschäftsführer: Max Mustermann</p><p><strong>Kontakt:</strong><br>Telefon: +49 (0) 123 456789<br>E-Mail: info@breakpilot.app</p><p><strong>Registereintrag:</strong><br>Eintragung im Handelsregister<br>Registergericht: Amtsgericht Musterstadt<br>Registernummer: HRB 12345</p><p><strong>Umsatzsteuer-ID:</strong><br>Umsatzsteuer-Identifikationsnummer gemäß § 27 a UStG: DE123456789</p><p><strong>Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV:</strong><br>Max Mustermann<br>Musterstraße 1<br>12345 Musterstadt</p>',
en: '<h3>Legal Notice</h3><p><strong>Information according to § 5 TMG:</strong></p><p>BreakPilot UG (limited liability)<br>Musterstraße 1<br>12345 Musterstadt<br>Germany</p><p><strong>Represented by:</strong><br>Managing Director: Max Mustermann</p><p><strong>Contact:</strong><br>Phone: +49 (0) 123 456789<br>Email: info@breakpilot.app</p><p><strong>Register entry:</strong><br>Entry in the commercial register<br>Register court: Amtsgericht Musterstadt<br>Register number: HRB 12345</p><p><strong>VAT ID:</strong><br>VAT identification number according to § 27 a UStG: DE123456789</p><p><strong>Responsible for content according to § 55 Abs. 2 RStV:</strong><br>Max Mustermann<br>Musterstraße 1<br>12345 Musterstadt</p>'
};
return imprint[lang] || imprint.de;
}
// Save cookie preferences
function saveCookiePreferences() {
const prefs = {};
const checkboxes = document.querySelectorAll('#cookie-categories-container input[type="checkbox"]');
checkboxes.forEach(cb => {
const name = cb.id.replace('cookie-', '');
if (name && !cb.disabled) {
prefs[name] = cb.checked;
}
});
localStorage.setItem('bp_cookie_consent', JSON.stringify(prefs));
localStorage.setItem('bp_cookie_consent_date', new Date().toISOString());
// TODO: Send to consent service if user is logged in
alert('Cookie-Einstellungen gespeichert!');
}
"""