Files
Val-Friendbook/public/app.js
T
2026-10-18 19:47:56 +02:00

215 lines
7.2 KiB
JavaScript

(() => {
"use strict";
const MOODS = ["😎", "🔥", "💀", "😭", "🥲", "✨", "😤", "🖤", "🫠", "👾"];
const MAX_BADGES = 5;
// ---------------- tabs ----------------
const tabForm = document.getElementById("tab-form");
const tabGallery = document.getElementById("tab-gallery");
const panelForm = document.getElementById("panel-form");
const panelGallery = document.getElementById("panel-gallery");
function showPanel(which) {
const showForm = which === "form";
panelForm.classList.toggle("is-hidden", !showForm);
panelGallery.classList.toggle("is-hidden", showForm);
tabForm.classList.toggle("is-active", showForm);
tabGallery.classList.toggle("is-active", !showForm);
tabForm.setAttribute("aria-selected", String(showForm));
tabGallery.setAttribute("aria-selected", String(!showForm));
if (!showForm) loadEntries();
}
tabForm.addEventListener("click", () => showPanel("form"));
tabGallery.addEventListener("click", () => showPanel("gallery"));
// ---------------- mood picker ----------------
const moodPicker = document.querySelector(".mood-picker");
const moodInput = document.getElementById("mood");
MOODS.forEach((emoji, i) => {
const btn = document.createElement("button");
btn.type = "button";
btn.className = "mood-btn";
btn.textContent = emoji;
btn.setAttribute("role", "radio");
btn.setAttribute("aria-pressed", i === MOODS.length - 1 ? "true" : "false"); // default last = 🖤
btn.setAttribute("aria-label", `Mood: ${emoji}`);
btn.addEventListener("click", () => {
moodPicker.querySelectorAll(".mood-btn").forEach((b) => b.setAttribute("aria-pressed", "false"));
btn.setAttribute("aria-pressed", "true");
moodInput.value = emoji;
});
moodPicker.appendChild(btn);
});
moodInput.value = MOODS[MOODS.length - 1];
// ---------------- badge preview ----------------
const badgeInput = document.getElementById("badges");
const badgePreview = document.getElementById("badge-preview");
const formError = document.getElementById("form-error");
const formSuccess = document.getElementById("form-success");
badgeInput.addEventListener("change", () => {
badgePreview.innerHTML = "";
const files = Array.from(badgeInput.files || []);
if (files.length > MAX_BADGES) {
showError(`Please choose at most ${MAX_BADGES} images.`);
badgeInput.value = "";
return;
}
clearError();
files.forEach((file) => {
const url = URL.createObjectURL(file);
const img = document.createElement("img");
img.src = url;
img.alt = file.name;
img.onload = () => URL.revokeObjectURL(url);
badgePreview.appendChild(img);
});
});
function showError(msg) {
clearSuccess();
formError.textContent = msg;
formError.hidden = false;
}
function clearError() {
formError.hidden = true;
formError.textContent = "";
}
function showSuccess(msg) {
clearError();
formSuccess.textContent = msg;
formSuccess.hidden = false;
}
function clearSuccess() {
formSuccess.hidden = true;
formSuccess.textContent = "";
}
// ---------------- form submit ----------------
const form = document.getElementById("entry-form");
const submitBtn = document.getElementById("submit-btn");
form.addEventListener("submit", async (e) => {
e.preventDefault();
clearError();
clearSuccess();
const name = document.getElementById("name").value.trim();
if (!name) {
showError("Your Name is required.");
document.getElementById("name").focus();
return;
}
const formData = new FormData(form);
submitBtn.disabled = true;
submitBtn.querySelector("span").textContent = "ADDING...";
try {
const res = await fetch("/api/entries", { method: "POST", body: formData });
const data = await res.json().catch(() => ({}));
if (!res.ok) {
throw new Error(data.error || "Something went wrong submitting your page.");
}
form.reset();
badgePreview.innerHTML = "";
moodPicker.querySelectorAll(".mood-btn").forEach((b, i) =>
b.setAttribute("aria-pressed", i === MOODS.length - 1 ? "true" : "false")
);
moodInput.value = MOODS[MOODS.length - 1];
showSuccess("Thanks! Your page has been submitted and is waiting for approval — check back on \u201cEveryone's pages\u201d soon 💌");
} catch (err) {
showError(err.message);
} finally {
submitBtn.disabled = false;
submitBtn.querySelector("span").textContent = "ADD MY PAGE";
}
});
// ---------------- gallery ----------------
const galleryGrid = document.getElementById("gallery-grid");
const galleryStatus = document.getElementById("gallery-status");
function escapeHtml(str) {
const div = document.createElement("div");
div.textContent = str == null ? "" : String(str);
return div.innerHTML;
}
function ordinal(n) {
const s = ["th", "st", "nd", "rd"];
const v = n % 100;
return n + (s[(v - 20) % 10] || s[v] || s[0]);
}
function formatDate(ts) {
const d = new Date(ts);
const months = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December",
];
return `${ordinal(d.getDate())} ${months[d.getMonth()]} ${d.getFullYear()}`;
}
function renderRow(label, value) {
if (!value) return "";
return `<div class="entry-card__row"><span class="k">${escapeHtml(label)}</span><span class="v">${escapeHtml(value)}</span></div>`;
}
function renderCard(entry) {
const card = document.createElement("article");
card.className = "entry-card";
const badgesHtml = (entry.badges || [])
.map((src) => `<img src="${escapeHtml(src)}" alt="badge" loading="lazy" />`)
.join("");
card.innerHTML = `
<span class="entry-card__mood" aria-hidden="true">${escapeHtml(entry.mood || "🖤")}</span>
<h3 class="entry-card__name">${escapeHtml(entry.name)}</h3>
${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 ? `<div class="entry-card__badges">${badgesHtml}</div>` : ""}
<div class="entry-card__time">${escapeHtml(formatDate(entry.createdAt))}</div>
`;
return card;
}
async function loadEntries() {
galleryStatus.textContent = "flipping through pages...";
galleryGrid.innerHTML = "";
try {
const res = await fetch("/api/entries");
if (!res.ok) throw new Error("Could not load pages.");
const entries = await res.json();
if (!entries.length) {
galleryStatus.textContent = "no pages yet — be the first to sign the book!";
return;
}
galleryStatus.textContent = `${entries.length} page${entries.length === 1 ? "" : "s"} taped in so far`;
entries.forEach((entry) => galleryGrid.appendChild(renderCard(entry)));
} catch (err) {
galleryStatus.textContent = "hmm, couldn't load the pages. is the server running?";
}
}
// initial load in the background so the gallery is ready when switched to
loadEntries();
})();