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>
181 lines
6.1 KiB
Python
181 lines
6.1 KiB
Python
"""
|
||
School Module - Parent Onboarding Page
|
||
QR code landing page for parent registration
|
||
"""
|
||
|
||
from ..styles import SCHOOL_BASE_STYLES, ONBOARDING_STYLES
|
||
from ..templates import COMMON_SCRIPTS
|
||
|
||
|
||
def parent_onboarding() -> str:
|
||
"""Parent onboarding page (QR code landing)"""
|
||
# Onboarding page uses its own simplified styles without sidebar
|
||
styles = f"""
|
||
:root {{
|
||
--bp-primary: #6C1B1B;
|
||
--bp-bg: #F8F8F8;
|
||
--bp-surface: #FFFFFF;
|
||
--bp-text: #4A4A4A;
|
||
--bp-text-muted: #6B6B6B;
|
||
--bp-accent: #5ABF60;
|
||
--bp-border: #E0E0E0;
|
||
}}
|
||
|
||
* {{ box-sizing: border-box; margin: 0; padding: 0; }}
|
||
|
||
{ONBOARDING_STYLES}
|
||
"""
|
||
|
||
content = '''
|
||
<div class="onboarding-container">
|
||
<div class="logo">
|
||
<div class="logo-icon">BP</div>
|
||
<span class="logo-text">BreakPilot</span>
|
||
</div>
|
||
|
||
<div id="loading">
|
||
<div class="spinner"></div>
|
||
<p>QR-Code wird validiert...</p>
|
||
</div>
|
||
|
||
<div id="content" style="display: none;">
|
||
<h1>Eltern-Registrierung</h1>
|
||
<p class="subtitle">Sie wurden eingeladen, sich für die Schulkommunikation zu registrieren.</p>
|
||
|
||
<div id="error" class="error" style="display: none;"></div>
|
||
<div id="success" class="success" style="display: none;"></div>
|
||
|
||
<div class="info-card">
|
||
<div class="info-row">
|
||
<span class="info-label">Schule</span>
|
||
<span class="info-value" id="school-name">-</span>
|
||
</div>
|
||
<div class="info-row">
|
||
<span class="info-label">Klasse</span>
|
||
<span class="info-value" id="class-name">-</span>
|
||
</div>
|
||
<div class="info-row">
|
||
<span class="info-label">Kind</span>
|
||
<span class="info-value" id="student-name">-</span>
|
||
</div>
|
||
<div class="info-row">
|
||
<span class="info-label">Ihre Rolle</span>
|
||
<span class="info-value" id="role">Elternteil</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="checkbox-group">
|
||
<div class="checkbox-item">
|
||
<input type="checkbox" id="consent-terms" required>
|
||
<label class="checkbox-label" for="consent-terms">
|
||
Ich habe die <a href="/terms" target="_blank">Nutzungsbedingungen</a> gelesen und akzeptiere diese.
|
||
</label>
|
||
</div>
|
||
<div class="checkbox-item">
|
||
<input type="checkbox" id="consent-privacy" required>
|
||
<label class="checkbox-label" for="consent-privacy">
|
||
Ich habe die <a href="/privacy" target="_blank">Datenschutzerklärung</a> gelesen und stimme der Verarbeitung meiner Daten zu.
|
||
</label>
|
||
</div>
|
||
<div class="checkbox-item">
|
||
<input type="checkbox" id="consent-custody" required>
|
||
<label class="checkbox-label" for="consent-custody">
|
||
Ich bestätige, dass ich sorgeberechtigt für das oben genannte Kind bin.
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<button class="btn btn-primary" id="submit-btn" onclick="completeOnboarding()" disabled>
|
||
Registrierung abschließen
|
||
</button>
|
||
</div>
|
||
</div>'''
|
||
|
||
scripts = '''
|
||
<script>
|
||
const API_BASE = '/api/v1';
|
||
let tokenData = null;
|
||
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
const token = urlParams.get('token');
|
||
|
||
async function validateToken() {
|
||
if (!token) {
|
||
showError('Kein gültiger QR-Code. Bitte scannen Sie den QR-Code erneut.');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch(API_BASE + '/onboarding/validate?token=' + token);
|
||
const data = await response.json();
|
||
|
||
if (!response.ok || !data.valid) {
|
||
showError('Der QR-Code ist ungültig oder abgelaufen. Bitte wenden Sie sich an den Klassenlehrer.');
|
||
return;
|
||
}
|
||
|
||
tokenData = data;
|
||
|
||
document.getElementById('school-name').textContent = data.school_name;
|
||
document.getElementById('class-name').textContent = data.class_name;
|
||
document.getElementById('student-name').textContent = data.student_name;
|
||
document.getElementById('role').textContent = data.role === 'parent_representative' ? 'Elternvertreter' : 'Elternteil';
|
||
|
||
document.getElementById('loading').style.display = 'none';
|
||
document.getElementById('content').style.display = 'block';
|
||
|
||
} catch (error) {
|
||
showError('Ein Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.');
|
||
}
|
||
}
|
||
|
||
function showError(message) {
|
||
document.getElementById('loading').style.display = 'none';
|
||
document.getElementById('content').style.display = 'block';
|
||
document.getElementById('error').textContent = message;
|
||
document.getElementById('error').style.display = 'block';
|
||
}
|
||
|
||
document.querySelectorAll('input[type="checkbox"]').forEach(cb => {
|
||
cb.addEventListener('change', () => {
|
||
const allChecked = document.querySelectorAll('input[type="checkbox"]:checked').length === 3;
|
||
document.getElementById('submit-btn').disabled = !allChecked;
|
||
});
|
||
});
|
||
|
||
async function completeOnboarding() {
|
||
const btn = document.getElementById('submit-btn');
|
||
btn.disabled = true;
|
||
btn.textContent = 'Wird verarbeitet...';
|
||
|
||
try {
|
||
const returnUrl = encodeURIComponent(window.location.href);
|
||
window.location.href = '/app/login?return_to=' + returnUrl + '&onboarding=true';
|
||
} catch (error) {
|
||
showError('Ein Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.');
|
||
btn.disabled = false;
|
||
btn.textContent = 'Registrierung abschließen';
|
||
}
|
||
}
|
||
|
||
validateToken();
|
||
</script>'''
|
||
|
||
# This page doesn't use the standard base template with sidebar
|
||
return f'''<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>BreakPilot – Eltern-Onboarding</title>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||
<style>
|
||
{styles}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
{content}
|
||
{scripts}
|
||
</body>
|
||
</html>'''
|