Files
geograsim/App/profil.html
T
Adminator 8455a96674 Atlas: Versions-Tag rechts unten auf allen Hauptseiten
- App/version.json mit Commit-SHA + Build-Datum
- App/assets/js/version-tag.js — rendert kleines Tag rechts unten
- Eingebunden in schueler, teacher, index, login, admin, profil
- Ermoeglicht Vergleich lokal vs. geograsim.at auf einen Blick

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 18:06:42 +02:00

235 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GeoGraSim — Profil</title>
<link rel="icon" type="image/png" href="favicon-96x96.png" sizes="96x96"><link rel="icon" type="image/svg+xml" href="favicon.svg">
<link rel="stylesheet" href="assets/fonts/inter.css">
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Inter',sans-serif;background:#f7f6f3;min-height:100vh}
.topbar{background:#fff;padding:.6rem 1.2rem;display:flex;align-items:center;gap:.8rem;border-bottom:1px solid rgba(0,0,0,.06);position:sticky;top:0;z-index:10}
.topbar h1{font-size:.95rem;font-weight:800;color:#4a7c8a;flex:1;display:flex;align-items:center;gap:.5rem}
.topbar h1 img{height:24px}
.topbar .btn-sm{padding:.3rem .6rem;border:1px solid rgba(0,0,0,.08);border-radius:6px;background:#fff;font-size:.68rem;font-weight:600;cursor:pointer;color:#4a4a4a;text-decoration:none}
.topbar .btn-sm:hover{background:#dae8ec;color:#4a7c8a}
.wrap{max-width:600px;margin:2rem auto;padding:0 1rem}
.card{background:#fff;border-radius:12px;padding:1.2rem;box-shadow:0 2px 8px rgba(0,0,0,.04);border:1px solid rgba(0,0,0,.04);margin-bottom:1rem}
.card-h{font-size:.62rem;font-weight:700;color:#4a7c8a;text-transform:uppercase;letter-spacing:.05em;margin-bottom:.6rem}
.avatar-section{text-align:center;padding:1rem 0}
.avatar-current{font-size:4rem;cursor:pointer;transition:transform .2s}
.avatar-current:hover{transform:scale(1.15)}
.avatar-name{font-size:1.1rem;font-weight:700;margin-top:.3rem}
.avatar-role{font-size:.72rem;color:#8a8a8a;margin-top:.1rem}
.avatar-picker{display:grid;grid-template-columns:repeat(8,1fr);gap:.3rem;margin-top:.8rem;display:none}
.avatar-picker.open{display:grid}
.avatar-opt{font-size:1.6rem;text-align:center;cursor:pointer;padding:.3rem;border-radius:8px;transition:all .15s}
.avatar-opt:hover{background:#dae8ec;transform:scale(1.2)}
.avatar-opt.active{background:#4a7c8a;border-radius:8px}
.field{margin-bottom:.8rem}
.field label{display:block;font-size:.68rem;font-weight:600;color:#4a4a4a;margin-bottom:.15rem}
.field input{width:100%;padding:.45rem .65rem;border:1.5px solid rgba(0,0,0,.1);border-radius:7px;font-size:.8rem;font-family:inherit;outline:none;transition:border .15s}
.field input:focus{border-color:#4a7c8a}
.field input:disabled{background:#f7f6f3;color:#8a8a8a}
.btn{padding:.45rem .9rem;border:none;border-radius:7px;background:#4a7c8a;color:#fff;font-size:.75rem;font-weight:700;cursor:pointer;transition:background .15s}
.btn:hover{background:#3a6470}
.btn-danger{background:#c07a6b}
.btn-danger:hover{background:#a05a4b}
.msg{font-size:.72rem;padding:.35rem .6rem;border-radius:6px;margin-top:.5rem;display:none}
.msg.ok{display:block;background:#dceadd;color:#5a8a5e}
.msg.err{display:block;background:#f2ddd8;color:#c07a6b}
.info-row{display:flex;justify-content:space-between;padding:.3rem 0;border-bottom:1px solid rgba(0,0,0,.03);font-size:.78rem}
.info-row:last-child{border:none}
.info-label{color:#8a8a8a;font-weight:500}
.info-val{font-weight:600}
</style>
</head>
<body>
<div class="topbar">
<h1><img src="assets/img/geograsim_t.svg" alt="GeoGraSim" style="height:30px"> Mein Profil</h1>
<a href="schueler.html" class="btn-sm" id="nav-home">Cockpit</a>
<button class="btn-sm" onclick="logout()">Abmelden</button>
</div>
<div class="wrap" id="content">
<div style="text-align:center;padding:3rem;color:#8a8a8a">Laden...</div>
</div>
<script>
var BASE = (window.__GGS__?.baseUrl) || '';
if (!BASE) { var p = location.pathname, i = p.indexOf('/geograsim'); if (i !== -1) { var rest = p.substring(i + '/geograsim'.length); BASE = rest.indexOf('/App/') === 0 ? p.substring(0, i) + '/geograsim/App' : p.substring(0, i) + '/geograsim'; } }
var EMOJI_OPTIONS = [
'🧑‍🎓','👩‍🎓','👨‍🎓','🧑‍🏫','👩‍🏫','👨‍🏫',
'👩','👨','🧑','👧','👦','👶',
'👩‍🦰','👨‍🦱','👩‍🦳','👱‍♀️','👱‍♂️','🧔',
'🦊','🐱','🐶','🐻','🐼','🦁',
'🐸','🐧','🦉','🐝','🦋','🐢','🐬',
'🌟','🌈','🔥','❄️','🌺','🍀',
'🎵','🎮','⚽','🏀','🎨','📚','🔬','🌍','🚀','🏔️'
];
async function api(endpoint, data) {
var opts = data !== undefined
? {method:'POST',headers:{'Content-Type':'application/json'},credentials:'same-origin',body:JSON.stringify(data)}
: {credentials:'same-origin'};
var r = await fetch(BASE + '/api/' + endpoint, opts);
return await r.json();
}
function logout() {
api('auth', {action:'logout'}).then(function() { location.href = 'login.html'; });
}
async function loadProfile() {
var r = await api('profile');
if (r.role === 'guest' || !r.profile) {
location.href = 'login.html';
return;
}
if (r.role === 'student') {
renderStudentProfile(r.profile);
document.getElementById('nav-home').href = 'schueler.html';
document.getElementById('nav-home').textContent = 'Cockpit';
} else if (r.role === 'teacher') {
renderTeacherProfile(r.profile);
document.getElementById('nav-home').href = 'teacher.html';
document.getElementById('nav-home').textContent = 'Dashboard';
}
}
function renderStudentProfile(p) {
var html = '';
// Avatar + Name
html += '<div class="card"><div class="avatar-section">';
html += '<div class="avatar-current" id="avatar-btn" onclick="togglePicker()">' + (p.emoji_avatar || '🧑‍🎓') + '</div>';
html += '<div class="avatar-name">' + (p.display_name || p.username) + '</div>';
html += '<div class="avatar-role">Klasse ' + (p.class_name || '') + '</div>';
html += '<div class="avatar-picker" id="avatar-picker">';
EMOJI_OPTIONS.forEach(function(e) {
var cls = e === (p.emoji_avatar || '🧑‍🎓') ? ' active' : '';
html += '<div class="avatar-opt' + cls + '" onclick="selectAvatar(\'' + e + '\')">' + e + '</div>';
});
html += '</div></div></div>';
// Info
html += '<div class="card"><div class="card-h">Kontoinformationen</div>';
html += '<div class="info-row"><span class="info-label">Benutzername</span><span class="info-val">' + p.username + '</span></div>';
html += '<div class="info-row"><span class="info-label">Klasse</span><span class="info-val">' + (p.class_name || '—') + '</span></div>';
html += '<div class="info-row"><span class="info-label">Klassencode</span><span class="info-val">' + (p.join_code || '—') + '</span></div>';
html += '<div class="info-row"><span class="info-label">Dabei seit</span><span class="info-val">' + new Date(p.created_at).toLocaleDateString('de-AT') + '</span></div>';
html += '</div>';
// Anzeigename ändern
html += '<div class="card"><div class="card-h">Anzeigename</div>';
html += '<div class="field"><input type="text" id="pf-name" value="' + (p.display_name || '') + '" placeholder="Dein Anzeigename"></div>';
html += '<button class="btn" onclick="saveName()">Speichern</button>';
html += '<div class="msg" id="msg-name"></div>';
html += '</div>';
document.getElementById('content').innerHTML = html;
}
function renderTeacherProfile(p) {
var html = '';
html += '<div class="card"><div class="avatar-section">';
html += '<div class="avatar-current" id="avatar-btn" onclick="togglePicker()">' + (p.emoji_avatar || '👩‍🏫') + '</div>';
html += '<div class="avatar-name">' + (p.display_name || '') + '</div>';
html += '<div class="avatar-role">Lehrperson' + (p.school_name ? ' · ' + p.school_name : '') + '</div>';
html += '<div class="avatar-picker" id="avatar-picker">';
EMOJI_OPTIONS.forEach(function(e) {
var cls = e === (p.emoji_avatar || '👩‍🏫') ? ' active' : '';
html += '<div class="avatar-opt' + cls + '" onclick="selectAvatar(\'' + e + '\')">' + e + '</div>';
});
html += '</div></div></div>';
html += '<div class="card"><div class="card-h">Kontoinformationen</div>';
html += '<div class="info-row"><span class="info-label">E-Mail</span><span class="info-val">' + (p.email || '—') + '</span></div>';
html += '<div class="info-row"><span class="info-label">Schule</span><span class="info-val">' + (p.school_name || '—') + '</span></div>';
html += '<div class="info-row"><span class="info-label">Dabei seit</span><span class="info-val">' + new Date(p.created_at).toLocaleDateString('de-AT') + '</span></div>';
html += '</div>';
html += '<div class="card"><div class="card-h">Profil bearbeiten</div>';
html += '<div class="field"><label>Anzeigename</label><input type="text" id="pf-name" value="' + (p.display_name || '') + '"></div>';
html += '<div class="field"><label>E-Mail</label><input type="email" id="pf-email" value="' + (p.email || '') + '"></div>';
html += '<div class="field"><label>Schule</label><input type="text" id="pf-school" value="' + (p.school_name || '') + '"></div>';
html += '<button class="btn" onclick="saveTeacher()">Speichern</button>';
html += '<div class="msg" id="msg-teacher"></div>';
html += '</div>';
html += '<div class="card"><div class="card-h">Passwort ändern</div>';
html += '<div class="field"><label>Altes Passwort</label><input type="password" id="pf-oldpw"></div>';
html += '<div class="field"><label>Neues Passwort (mind. 8 Zeichen)</label><input type="password" id="pf-newpw"></div>';
html += '<button class="btn" onclick="changePw()">Passwort ändern</button>';
html += '<div class="msg" id="msg-pw"></div>';
html += '</div>';
html += '<div class="card">';
html += '<a href="teacher.html" class="btn" style="text-decoration:none;display:inline-block">Zum Dashboard</a>';
html += '</div>';
html += '<div class="card" style="border-color:#f2ddd8">';
html += '<div class="card-h" style="color:#c07a6b">Konto löschen</div>';
html += '<p style="font-size:.7rem;color:#8a8a8a;margin-bottom:.5rem">Ihr Konto wird deaktiviert. Ihre Klassen bleiben erhalten, Schüler*innen werden zu Autodidakten.</p>';
html += '<button class="btn" style="background:#c07a6b" onclick="deleteAccount()">Konto endgültig löschen</button>';
html += '</div>';
document.getElementById('content').innerHTML = html;
}
function togglePicker() {
document.getElementById('avatar-picker').classList.toggle('open');
}
async function selectAvatar(emoji) {
document.getElementById('avatar-btn').textContent = emoji;
document.querySelectorAll('.avatar-opt').forEach(function(o) { o.classList.toggle('active', o.textContent === emoji); });
await api('profile', {emojiAvatar: emoji});
}
async function saveName() {
var name = document.getElementById('pf-name').value.trim();
if (!name) return;
var r = await api('profile', {displayName: name});
var m = document.getElementById('msg-name');
m.className = r.error ? 'msg err' : 'msg ok';
m.textContent = r.error || 'Gespeichert!';
}
async function saveTeacher() {
var name = document.getElementById('pf-name').value.trim();
var email = document.getElementById('pf-email').value.trim();
var school = document.getElementById('pf-school').value.trim();
var r = await api('profile', {displayName: name, email: email, schoolName: school});
var m = document.getElementById('msg-teacher');
m.className = r.error ? 'msg err' : 'msg ok';
m.textContent = r.error || 'Gespeichert!';
}
async function changePw() {
var old = document.getElementById('pf-oldpw').value;
var pw = document.getElementById('pf-newpw').value;
if (!old || !pw) return;
var r = await api('profile', {oldPassword: old, newPassword: pw});
var m = document.getElementById('msg-pw');
m.className = r.error ? 'msg err' : 'msg ok';
m.textContent = r.error || 'Passwort geändert!';
if (!r.error) { document.getElementById('pf-oldpw').value = ''; document.getElementById('pf-newpw').value = ''; }
}
async function deleteAccount() {
if (!confirm('Sind Sie sicher? Ihr Konto wird deaktiviert. Diese Aktion kann nicht rückgängig gemacht werden.')) return;
if (!confirm('Wirklich löschen? Alle Ihre Klassen werden archiviert.')) return;
var r = await api('profile', {action: 'delete_account'});
if (r.error) { alert(r.error); return; }
alert('Konto wurde gelöscht.');
location.href = 'index.html';
}
loadProfile();
</script>
<script src="assets/js/version-tag.js" defer></script>
</body>
</html>