Rollenbasiertes Postfach, große Startseite, Casper-Fix, Streamplan/TikTok-Korrekturen

This commit is contained in:
2026-07-30 20:25:56 +02:00
parent f8e4cf82db
commit b1712ec33b
10 changed files with 496 additions and 82 deletions
+170 -25
View File
@@ -7,17 +7,6 @@
<title>Postfach — DOGFATHER UNIVERSE</title>
<meta name="description" content="Internes Bewerbungs-Postfach — nur mit Code zugänglich." />
<link rel="icon" type="image/svg+xml" href="assets/img/favicon.svg" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="DOGFATHER UNIVERSE" />
<meta property="og:title" content="Postfach — DOGFATHER UNIVERSE" />
<meta property="og:description" content="Internes Bewerbungs-Postfach — nur mit Code zugänglich." />
<meta property="og:image" content="https://REPLACE-WITH-YOUR-DOMAIN.tld/assets/img/og-cover.jpg" />
<meta property="og:url" content="https://REPLACE-WITH-YOUR-DOMAIN.tld/postfach.html" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Postfach — DOGFATHER UNIVERSE" />
<meta name="twitter:description" content="Internes Bewerbungs-Postfach — nur mit Code zugänglich." />
<meta name="twitter:image" content="https://REPLACE-WITH-YOUR-DOMAIN.tld/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css" />
@@ -36,7 +25,7 @@
</section>
<section class="section-tight">
<div class="container" style="max-width:520px;">
<div class="container" style="max-width:520px;" id="gate-container">
<form id="code-gate" class="app-form" novalidate>
<div class="field">
<label for="code-input">Zugangscode</label>
@@ -56,6 +45,15 @@
</div>
</section>
<!-- ===== Eingeloggt: Kopfzeile mit Rolle + Logout ===== -->
<section class="section-tight" id="session-bar" style="display:none;padding-top:0;">
<div class="container" style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:1rem;">
<span class="badge rank-1" id="role-badge">Eingeloggt</span>
<button class="btn btn-outline" id="logout-btn" type="button">Abmelden</button>
</div>
</section>
<!-- ===== Mailbox (für alle mit gültigem Code) ===== -->
<section class="section-tight" id="inbox-section" style="display:none;">
<div class="container">
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:1rem;margin-bottom:1.4rem;">
@@ -65,6 +63,40 @@
<div id="inbox-list" class="grid grid-2"></div>
</div>
</section>
<!-- ===== Rollen-Verwaltung (NUR für Dogi/Owner sichtbar) ===== -->
<section id="admin-section" style="display:none;">
<div class="container">
<h2 class="text-center">Rollen verwalten</h2>
<p class="lead text-center" style="max-width:60ch;margin:0 auto;">
Vergib Codes an Rechte Hand, Linke Hand oder wen du sonst reinlassen willst. Diese Codes
zeigen <strong>ausschließlich die Bewerbungen</strong> — nichts an der Website selbst kann
damit verändert oder gelöscht werden, dafür gibt es hier schlicht keine Funktion.
</p>
<form id="role-create-form" class="app-form" style="max-width:520px;margin:1.6rem auto;" novalidate>
<div class="field">
<label for="role-select">Rolle</label>
<select id="role-select" name="role">
<option value="Rechte Hand">Rechte Hand</option>
<option value="Linke Hand">Linke Hand</option>
<option value="Helfer">Sonstige / frei</option>
</select>
</div>
<div class="field">
<label for="role-label">Für wen? (Name/Bezeichnung)</label>
<input id="role-label" name="label" type="text" placeholder="z.B. VanVan" required />
</div>
<button class="btn btn-primary" type="submit">Neuen Code erzeugen</button>
<div class="form-status" id="role-create-status"></div>
</form>
<div class="todo-note" id="new-code-reveal" style="display:none;max-width:520px;margin:0 auto 2rem;"></div>
<h3 class="text-center">Bestehende Codes</h3>
<div id="roles-list" class="grid grid-2"></div>
</div>
</section>
</main>
<div id="site-footer"></div>
@@ -75,12 +107,21 @@
const API_BASE_URL = "";
document.addEventListener("DOMContentLoaded", () => {
const gateContainer = document.getElementById("gate-container");
const form = document.getElementById("code-gate");
const status = document.getElementById("gate-status");
const sessionBar = document.getElementById("session-bar");
const roleBadge = document.getElementById("role-badge");
const logoutBtn = document.getElementById("logout-btn");
const inboxSection = document.getElementById("inbox-section");
const inboxList = document.getElementById("inbox-list");
const refreshBtn = document.getElementById("refresh-btn");
const notConfigured = document.getElementById("not-configured-note");
const adminSection = document.getElementById("admin-section");
const roleCreateForm = document.getElementById("role-create-form");
const roleCreateStatus = document.getElementById("role-create-status");
const newCodeReveal = document.getElementById("new-code-reveal");
const rolesList = document.getElementById("roles-list");
if (!API_BASE_URL) {
notConfigured.style.display = "block";
@@ -88,7 +129,7 @@ document.addEventListener("DOMContentLoaded", () => {
notConfigured.style.display = "none";
}
let lastCode = "";
let session = { code: "", isOwner: false, roleLabel: "" };
function renderInbox(bewerbungen) {
if (!bewerbungen.length) {
@@ -111,44 +152,148 @@ document.addEventListener("DOMContentLoaded", () => {
.join("");
}
async function loadInbox(code) {
async function loadInbox() {
status.className = "form-status show";
status.textContent = "Lade Postfach …";
try {
const res = await fetch(`${API_BASE_URL}/postfach`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code }),
body: JSON.stringify({ code: session.code }),
});
const json = await res.json();
if (!res.ok || !json.ok) {
const data = await res.json();
if (!res.ok || !data.ok) {
status.textContent = "Falscher Code oder Postfach nicht erreichbar.";
status.className = "form-status show error";
inboxSection.style.display = "none";
return;
return false;
}
status.className = "form-status";
session.isOwner = data.isOwner;
session.roleLabel = data.roleLabel;
gateContainer.style.display = "none";
sessionBar.style.display = "block";
inboxSection.style.display = "block";
renderInbox(json.bewerbungen);
roleBadge.textContent = `Eingeloggt als: ${data.roleLabel}`;
renderInbox(data.bewerbungen);
if (data.isOwner) {
adminSection.style.display = "block";
loadRoles();
} else {
adminSection.style.display = "none";
}
return true;
} catch {
status.textContent = "Postfach gerade nicht erreichbar. Bitte später erneut versuchen.";
status.className = "form-status show error";
return false;
}
}
form.addEventListener("submit", (e) => {
async function loadRoles() {
try {
const res = await fetch(`${API_BASE_URL}/admin/roles/list`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ownerCode: session.code }),
});
const data = await res.json();
if (!res.ok || !data.ok) {
rolesList.innerHTML = `<p class="muted">Konnte Codes nicht laden.</p>`;
return;
}
if (!data.roles.length) {
rolesList.innerHTML = `<p class="muted">Noch keine Helfer-Codes vergeben.</p>`;
return;
}
rolesList.innerHTML = data.roles
.map(
(r) => `
<div class="card">
<span class="tag">${r.role}</span>
<h3>${r.label}</h3>
<p class="small muted">Erstellt: ${new Date(r.createdAt).toLocaleDateString("de-DE")}</p>
<button class="btn btn-outline revoke-btn" data-key="${r.keyId}" type="button">Code entziehen</button>
</div>`
)
.join("");
rolesList.querySelectorAll(".revoke-btn").forEach((btn) => {
btn.addEventListener("click", async () => {
if (!confirm("Diesen Code wirklich entziehen? Die Person kann sich danach nicht mehr einloggen.")) return;
await fetch(`${API_BASE_URL}/admin/roles/revoke`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ownerCode: session.code, keyId: btn.dataset.key }),
});
loadRoles();
});
});
} catch {
rolesList.innerHTML = `<p class="muted">Konnte Codes nicht laden.</p>`;
}
}
form.addEventListener("submit", async (e) => {
e.preventDefault();
if (!API_BASE_URL) {
status.textContent = "Postfach ist noch nicht eingerichtet (siehe Hinweis unten).";
status.className = "form-status show error";
return;
}
lastCode = document.getElementById("code-input").value;
loadInbox(lastCode);
session.code = document.getElementById("code-input").value;
const ok = await loadInbox();
if (ok) {
status.className = "form-status";
form.reset();
}
});
logoutBtn.addEventListener("click", () => {
session = { code: "", isOwner: false, roleLabel: "" };
gateContainer.style.display = "block";
sessionBar.style.display = "none";
inboxSection.style.display = "none";
adminSection.style.display = "none";
status.textContent = "";
status.className = "form-status";
});
refreshBtn.addEventListener("click", () => {
if (lastCode) loadInbox(lastCode);
if (session.code) loadInbox();
});
roleCreateForm.addEventListener("submit", async (e) => {
e.preventDefault();
const role = document.getElementById("role-select").value;
const label = document.getElementById("role-label").value.trim();
if (!label) return;
try {
const res = await fetch(`${API_BASE_URL}/admin/roles/create`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ownerCode: session.code, role, label }),
});
const data = await res.json();
if (!res.ok || !data.ok) {
roleCreateStatus.textContent = "Konnte keinen Code erzeugen.";
roleCreateStatus.className = "form-status show error";
return;
}
newCodeReveal.style.display = "block";
newCodeReveal.innerHTML = `
<strong>Neuer Code für ${data.label} (${data.role}):</strong><br>
<span style="font-size:1.3rem;font-family:monospace;letter-spacing:.05em;">${data.code}</span><br>
<span class="small">Jetzt sichern/weitergeben — dieser Code wird nirgends mehr angezeigt!</span>`;
roleCreateStatus.textContent = "";
roleCreateStatus.className = "form-status";
roleCreateForm.reset();
loadRoles();
} catch {
roleCreateStatus.textContent = "Postfach gerade nicht erreichbar.";
roleCreateStatus.className = "form-status show error";
}
});
});
</script>