(() => { "use strict"; const STORAGE_KEY = "friendbook_admin_pw"; const passwordGate = document.getElementById("password-gate"); const passwordForm = document.getElementById("password-form"); const passwordInput = document.getElementById("admin-password"); const passwordError = document.getElementById("password-error"); const adminMain = document.getElementById("admin-main"); const logoutBtn = document.getElementById("logout-btn"); const pendingGrid = document.getElementById("pending-grid"); const pendingStatus = document.getElementById("pending-status"); const approvedGrid = document.getElementById("approved-grid"); const approvedStatus = document.getElementById("approved-status"); // ---------------- password storage (per-tab only) ---------------- function getPassword() { return sessionStorage.getItem(STORAGE_KEY) || ""; } function setPassword(pw) { sessionStorage.setItem(STORAGE_KEY, pw); } function clearPassword() { sessionStorage.removeItem(STORAGE_KEY); } // ---------------- gate / main toggling ---------------- function showGate(errorMsg) { adminMain.hidden = true; passwordGate.hidden = false; if (errorMsg) { passwordError.textContent = errorMsg; passwordError.hidden = false; } else { passwordError.hidden = true; passwordError.textContent = ""; } passwordInput.value = ""; passwordInput.focus(); } function showAdmin() { passwordGate.hidden = true; adminMain.hidden = false; loadEntries(); } // ---------------- authenticated fetch helper ---------------- async function adminFetch(url, options) { const opts = options || {}; const headers = Object.assign({}, opts.headers, { "x-admin-password": getPassword() }); const res = await fetch(url, Object.assign({}, opts, { headers })); if (res.status === 401) { clearPassword(); showGate("Incorrect password. Try again."); throw new Error("unauthorized"); } return res; } // ---------------- login form ---------------- passwordForm.addEventListener("submit", async (e) => { e.preventDefault(); const pw = passwordInput.value; setPassword(pw); try { const res = await fetch("/api/admin/login", { method: "POST", headers: { "x-admin-password": pw }, }); if (!res.ok) { clearPassword(); showGate("Incorrect password. Try again."); return; } showAdmin(); } catch (err) { showGate("Could not reach the server. Is it running?"); } }); logoutBtn.addEventListener("click", () => { clearPassword(); showGate(); }); // ---------------- rendering ---------------- function escapeHtml(str) { const div = document.createElement("div"); div.textContent = str == null ? "" : String(str); return div.innerHTML; } function renderRow(label, value) { if (!value) return ""; return `
${escapeHtml(label)}${escapeHtml(value)}
`; } function renderEntry(entry) { const card = document.createElement("article"); card.className = "entry-card admin-card"; const badgesHtml = (entry.badges || []) .map((src) => `badge`) .join(""); card.innerHTML = `

${escapeHtml(entry.name)}

${renderRow("Fun fact", entry.funFact)} ${renderRow("Favourite colour", entry.favColor)} ${renderRow("Favourite music genre", entry.favGenre)} ${renderRow("Favourite agent", entry.petPeeves)} ${renderRow("On Valo", entry.merzHate)} ${renderRow("IGN", entry.socialLink)} ${badgesHtml ? `
${badgesHtml}
` : ""}
${escapeHtml(new Date(entry.createdAt).toLocaleString())}
`; const actions = card.querySelector(".admin-actions"); if (entry.status === "pending") { const approveBtn = document.createElement("button"); approveBtn.type = "button"; approveBtn.className = "btn-admin btn-admin--approve"; approveBtn.textContent = "Approve"; approveBtn.addEventListener("click", () => act(entry.id, "approve", card)); const denyBtn = document.createElement("button"); denyBtn.type = "button"; denyBtn.className = "btn-admin btn-admin--deny"; denyBtn.textContent = "Deny"; denyBtn.addEventListener("click", () => act(entry.id, "deny", card)); actions.append(approveBtn, denyBtn); } else { const removeBtn = document.createElement("button"); removeBtn.type = "button"; removeBtn.className = "btn-admin btn-admin--deny"; removeBtn.textContent = "Remove"; removeBtn.addEventListener("click", () => act(entry.id, "deny", card)); actions.append(removeBtn); } return card; } async function act(id, action, card) { card.classList.add("is-busy"); try { const res = await adminFetch(`/api/admin/entries/${id}/${action}`, { method: "POST" }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data.error || "Something went wrong."); loadEntries(); } catch (err) { if (err.message !== "unauthorized") { alert(err.message); card.classList.remove("is-busy"); } } } async function loadEntries() { pendingStatus.textContent = "loading..."; approvedStatus.textContent = "loading..."; pendingGrid.innerHTML = ""; approvedGrid.innerHTML = ""; try { const res = await adminFetch("/api/admin/entries"); if (!res.ok) throw new Error("Could not load entries."); const entries = await res.json(); const pending = entries.filter((e) => e.status === "pending"); const approved = entries.filter((e) => e.status === "approved"); pendingStatus.textContent = pending.length ? `${pending.length} waiting for review` : "nothing waiting — inbox zero (◡‿◡)"; approvedStatus.textContent = approved.length ? `${approved.length} live on the site` : "no approved pages yet"; pending.forEach((entry) => pendingGrid.appendChild(renderEntry(entry))); approved.forEach((entry) => approvedGrid.appendChild(renderEntry(entry))); } catch (err) { if (err.message !== "unauthorized") { pendingStatus.textContent = "couldn't load entries. is the server running?"; approvedStatus.textContent = ""; } } } // ---------------- boot ---------------- if (getPassword()) { showAdmin(); } else { showGate(); } })();