"Stimmen" verwandelt: Fans reichen selbst Testimonials ein
Nutzer-Wunsch 20.08.2026: "eine richtig geile kachel ... wo die leute mit namen und tiktok namen mir eine nachricht schreiben können die ich in die verwaltung kriege, und dann kann ich die ausgewählten über die verwaltungsseite auf die website hinzufügen ... das wird mega persoenlich zu den fans, bitte wirklich krass geil speziell." - Neue, augenschonend gestaltete Einreich-Kachel auf stimmen.html: Name, TikTok-Name (optional), Nachricht -- landet NIE automatisch oeffentlich, sondern immer erst als Entwurf in der Verwaltung. Warmer Babyblau/Rosé-Farbverlauf, wandernder Lichtschein, pulsierendes Herz -- ersetzt die 3 ewigen "Hier steht bald..."-Platzhalterkarten. - Neue Verwaltungs-Sektion "💬 Stimmen verwalten": Warteschlange (offene zuerst), Freigeben/Ablehnen/Löschen pro Eintrag. - Freigegebene Stimmen erscheinen automatisch in einer neuen, spezielleren Zitat-Kartenoptik (großes Anführungszeichen, Name + TikTok-Chip) -- Abschnitt bleibt komplett unsichtbar, solange keine einzige freigegeben wurde. - Backend: neue Tabelle `testimonials` (Migration 0006), routes/ testimonials.js (oeffentliches Einreichen + Lesen freigegebener, admin-Warteschlange + Status/Loeschen mit neuer TESTIMONIALS_MANAGE- Berechtigung). 18 automatisierte Tests gegen eine Fake-DB bestanden. - Alles per Playwright visuell durchgespielt: leerer Zustand, Einreichen -> Erfolgsmeldung, freigegebene Stimmen-Anzeige, Verwaltungs-Warteschlange mit allen Aktionen. Backend-Teil (server-internal/, cloudflare-worker/migrations/) noch ohne Deploy-Zugriff -- Dogi muss ihn manuell auf dogiintern ausrollen. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
/* =====================================================================
|
||||
stimmen.js — NUR für stimmen.html (20.08.2026, Nutzer-Wunsch: "eine
|
||||
richtig geile kachel ... wo die leute mit namen und tiktok namen mir
|
||||
eine nachricht schreiben können die ich in die verwaltung kriege").
|
||||
Formular schickt eine Einreichung ab (landet als Entwurf, siehe
|
||||
server-internal/routes/testimonials.js), zweiter Teil lädt die bereits
|
||||
freigegebenen Stimmen und zeigt sie an — bleibt komplett unsichtbar,
|
||||
solange keine einzige freigegeben wurde. ===================================================================== */
|
||||
(function () {
|
||||
const API_BASE_URL = "https://postfach.dogfather-universe.com";
|
||||
|
||||
const formular = document.getElementById("stimmen-formular");
|
||||
if (formular) {
|
||||
const erfolgEl = document.getElementById("stimmen-erfolg");
|
||||
const fehlerEl = document.getElementById("stimmen-fehler");
|
||||
formular.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
erfolgEl.classList.remove("zeige");
|
||||
fehlerEl.classList.remove("zeige");
|
||||
|
||||
const name = document.getElementById("st-name").value.trim();
|
||||
const tiktokUsername = document.getElementById("st-tiktok").value.trim();
|
||||
const nachricht = document.getElementById("st-nachricht").value.trim();
|
||||
|
||||
const absendenBtn = formular.querySelector("button[type=submit]");
|
||||
absendenBtn.disabled = true;
|
||||
try {
|
||||
const res = await fetch(`${API_BASE_URL}/testimonials/submit`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name, tiktokUsername, nachricht }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!data.ok) {
|
||||
fehlerEl.textContent = data.error || "Einreichen hat leider nicht geklappt. Bitte versuch es nochmal.";
|
||||
fehlerEl.classList.add("zeige");
|
||||
return;
|
||||
}
|
||||
formular.hidden = true;
|
||||
erfolgEl.classList.add("zeige");
|
||||
} catch {
|
||||
fehlerEl.textContent = "Verbindung zum Server fehlgeschlagen. Bitte versuch es nochmal.";
|
||||
fehlerEl.classList.add("zeige");
|
||||
} finally {
|
||||
absendenBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
return String(text ?? "").replace(/[&<>"']/g, (z) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[z]));
|
||||
}
|
||||
|
||||
function ladeEchteStimmen() {
|
||||
fetch(`${API_BASE_URL}/testimonials`, { cache: "no-store" })
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
const liste = data && data.ok ? data.testimonials : [];
|
||||
if (!liste.length) return; // Abschnitt bleibt hidden, kein Platzhalter mehr
|
||||
const root = document.getElementById("stimmen-echte-root");
|
||||
const grid = document.getElementById("stimmen-echte-grid");
|
||||
grid.innerHTML = liste
|
||||
.map(
|
||||
(t) => `<div class="stimmen-karte">
|
||||
<p class="stimmen-karte-text">${escapeHtml(t.nachricht)}</p>
|
||||
<div class="stimmen-karte-fuss">
|
||||
<span class="stimmen-karte-name">${escapeHtml(t.name)}</span>
|
||||
${t.tiktokUsername ? `<span class="stimmen-karte-tiktok">@${escapeHtml(t.tiktokUsername)}</span>` : ""}
|
||||
</div>
|
||||
</div>`
|
||||
)
|
||||
.join("");
|
||||
root.hidden = false;
|
||||
})
|
||||
.catch(() => {
|
||||
/* Abschnitt bleibt einfach unsichtbar -- kein Fehler nötig. */
|
||||
});
|
||||
}
|
||||
ladeEchteStimmen();
|
||||
})();
|
||||
Reference in New Issue
Block a user