8455a96674
- 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>
235 lines
12 KiB
HTML
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>
|