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>
This commit is contained in:
180
backend/frontend/school/pages/parent_onboarding.py
Normal file
180
backend/frontend/school/pages/parent_onboarding.py
Normal file
@@ -0,0 +1,180 @@
|
||||
"""
|
||||
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>'''
|
||||
Reference in New Issue
Block a user