Files
geograsim/v2-platform/login.html
T
Adminator 1e51ef7def Nachtrag: alle bisher untracked Ordner + hängende Änderungen mit-committen
- Konzept/, didaktik_geografie/, didaktik_simulation/, v2-modules/, v2-platform/
- 12 code-workspace-Files
- STATUS-*.md
- viele M/D/R-Änderungen an bereits getrackten Files
- .gitignore verstärkt: **/.humaninput/, **/secret_keys.txt

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-08 02:27:02 +02:00

232 lines
8.1 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>GeoGraSim V2 — Anmelden</title>
<link rel="stylesheet" href="./assets/css/design-system-v2.css">
<style>
body {
background: linear-gradient(135deg, var(--ggs-fjord-dark), var(--ggs-fjord));
min-height: 100dvh;
display: flex; align-items: center; justify-content: center;
padding: max(16px, env(safe-area-inset-top)) 16px max(16px, env(safe-area-inset-bottom));
}
.login-card {
background: var(--ggs-white);
border-radius: var(--ggs-radius-lg);
box-shadow: var(--ggs-shadow-lg);
max-width: 440px; width: 100%;
padding: var(--ggs-gap-xl);
}
.login-header {
text-align: center;
margin-bottom: var(--ggs-gap-lg);
}
.login-header h1 {
font-size: 1.4rem; color: var(--ggs-fjord-dark);
margin-bottom: var(--ggs-gap-xs);
}
.login-header .sub {
font-size: 0.85rem; color: var(--ggs-text-muted);
}
.tabs {
display: flex; gap: var(--ggs-gap-xs);
border-bottom: 1px solid var(--ggs-border);
margin-bottom: var(--ggs-gap-lg);
}
.tab-btn {
flex: 1; padding: var(--ggs-gap-sm) var(--ggs-gap-md);
background: transparent; border: none; cursor: pointer;
font-size: 0.9rem; font-family: inherit; font-weight: 600;
color: var(--ggs-text-muted);
border-bottom: 3px solid transparent;
min-height: var(--ggs-touch-min);
}
.tab-btn.active {
color: var(--ggs-fjord-dark);
border-bottom-color: var(--ggs-fjord-dark);
}
.panel { display: none; }
.panel.active { display: block; }
.form-row { margin-bottom: var(--ggs-gap-md); }
.form-row label {
display: block; font-size: 0.78rem; font-weight: 600;
color: var(--ggs-text); margin-bottom: var(--ggs-gap-xs);
}
.form-row input { width: 100%; }
.msg {
padding: var(--ggs-gap-sm) var(--ggs-gap-md);
border-radius: var(--ggs-radius-md);
font-size: 0.85rem; margin-bottom: var(--ggs-gap-md);
display: none;
}
.msg.error { background: var(--ggs-coral-light); color: var(--ggs-coral); display: block; }
.msg.info { background: var(--ggs-fjord-light); color: var(--ggs-fjord-dark); display: block; }
.actions-row { margin-top: var(--ggs-gap-lg); display: flex; gap: var(--ggs-gap-md); }
.footer {
text-align: center; margin-top: var(--ggs-gap-lg);
font-size: 0.78rem; color: var(--ggs-text-muted);
}
.footer a { color: var(--ggs-fjord-dark); text-decoration: none; }
.footer a:hover { text-decoration: underline; }
</style>
</head>
<body>
<form class="login-card" id="loginForm" autocomplete="on" novalidate>
<div class="login-header">
<h1>GeoGraSim V2 — Anmelden</h1>
<p class="sub">Diese Beta-Version läuft parallel zur Hauptseite.</p>
</div>
<div class="tabs" role="tablist">
<button type="button" class="tab-btn active" id="tab-teacher" role="tab" aria-selected="true" aria-controls="panel-teacher">Lehrperson</button>
<button type="button" class="tab-btn" id="tab-student" role="tab" aria-selected="false" aria-controls="panel-student">Schüler*in</button>
</div>
<div class="msg" id="msg" role="status" aria-live="polite"></div>
<!-- Lehrperson-Panel -->
<div class="panel active" id="panel-teacher" role="tabpanel" aria-labelledby="tab-teacher">
<div class="form-row">
<label for="t-email">E-Mail oder Benutzername</label>
<input class="ggs-input" type="text" id="t-email" name="email"
autocomplete="username" required>
</div>
<div class="form-row">
<label for="t-pass">Passwort</label>
<input class="ggs-input" type="password" id="t-pass" name="password"
autocomplete="current-password" required>
</div>
</div>
<!-- Schüler*in-Panel -->
<div class="panel" id="panel-student" role="tabpanel" aria-labelledby="tab-student" hidden>
<div class="form-row">
<label for="s-user">Benutzername</label>
<input class="ggs-input" type="text" id="s-user" name="username" autocomplete="username">
</div>
<div class="form-row">
<label for="s-pass">Passwort</label>
<input class="ggs-input" type="password" id="s-pass" name="password" autocomplete="current-password">
</div>
<div class="form-row" id="row-classcode" hidden>
<label for="s-code">Klassencode (nur falls du in mehreren Klassen bist)</label>
<input class="ggs-input" type="text" id="s-code" name="classCode"
autocomplete="off" inputmode="text" style="text-transform:uppercase">
</div>
<div class="form-row" id="row-classpick" hidden>
<label for="s-class">Welche Klasse?</label>
<select class="ggs-select" id="s-class"></select>
</div>
</div>
<div class="actions-row">
<button type="submit" class="ggs-btn ggs-btn-primary ggs-btn-major" id="submitBtn" style="flex:1">
Anmelden
</button>
</div>
<div class="footer">
<p>Geht zur <a href="../">Hauptseite (V1)</a> oder zur <a href="../barrierefreiheit.html">Barrierefreiheitserklärung</a>.</p>
</div>
</form>
<script type="module">
import { auth } from './assets/js/auth-client.js';
// Wenn schon eingeloggt: Direkt-Redirect je nach Rolle
if (auth.token) {
location.href = (auth.role === 'student') ? './schueler.html' : './teacher.html';
}
const $ = id => document.getElementById(id);
let mode = 'teacher';
function switchTab(newMode) {
mode = newMode;
['teacher','student'].forEach(m => {
$('tab-' + m).classList.toggle('active', m === mode);
$('tab-' + m).setAttribute('aria-selected', m === mode ? 'true' : 'false');
const panel = $('panel-' + m);
panel.classList.toggle('active', m === mode);
panel.hidden = m !== mode;
});
hideMsg();
}
$('tab-teacher').addEventListener('click', () => switchTab('teacher'));
$('tab-student').addEventListener('click', () => switchTab('student'));
function showMsg(text, type) {
const m = $('msg');
m.textContent = text;
m.className = 'msg ' + type;
}
function hideMsg() {
const m = $('msg');
m.className = 'msg';
}
$('loginForm').addEventListener('submit', async (e) => {
e.preventDefault();
hideMsg();
$('submitBtn').disabled = true;
$('submitBtn').textContent = 'Anmelden …';
try {
let result;
if (mode === 'teacher') {
result = await auth.login('teacher', {
email: $('t-email').value.trim(),
password: $('t-pass').value,
});
} else {
// Klassencode: aus Picker oder Textfeld nehmen
const codeFromPick = $('s-class').value || '';
const codeFromText = $('s-code').value.trim().toUpperCase();
result = await auth.login('student', {
classCode: codeFromPick || codeFromText,
username: $('s-user').value.trim(),
password: $('s-pass').value,
});
}
showMsg('Angemeldet — wird weitergeleitet …', 'info');
setTimeout(() => {
location.href = (result.role === 'student') ? './schueler.html' : './teacher.html';
}, 300);
} catch (err) {
// Spezialfall: Server sagt „mehrere Klassen, bitte auswählen"
if (err.message && err.message.includes('mehreren Klassen')) {
try {
// Liste aus dem Response holen — auth.login wirft mit message, wir holen nochmal direkt
const r = await fetch('./php/api/auth/login.php', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
role: 'student',
username: $('s-user').value.trim(),
password: $('s-pass').value,
}),
});
const data = await r.json();
if (data.classes?.length) {
const sel = $('s-class');
sel.innerHTML = data.classes.map(c =>
`<option value="${c.joinCode}">${c.className} (${c.joinCode})</option>`
).join('');
$('row-classpick').hidden = false;
showMsg('Username gibt es in mehreren Klassen — bitte wählen.', 'info');
}
} catch { /* fallback unten */ }
} else {
showMsg(err.message || 'Anmeldung fehlgeschlagen.', 'error');
}
$('submitBtn').disabled = false;
$('submitBtn').textContent = 'Anmelden';
}
});
</script>
</body>
</html>