"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:
+77
-6
@@ -19,10 +19,10 @@
|
||||
<link rel="apple-touch-icon" href="assets/img/apple-touch-icon-admin.png" />
|
||||
<meta name="apple-mobile-web-app-title" content="DogiCrew-Verwaltung" />
|
||||
<meta name="theme-color" content="#0e0a16" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260820j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260820j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260820j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260820j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260820k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260820k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260820k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260820k" />
|
||||
<style>
|
||||
/* =====================================================================
|
||||
verwaltung.html — Dogis privater Command-Center-Bereich.
|
||||
@@ -844,6 +844,20 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== "Stimmen" verwalten (20.08.2026) — Nutzer-Wunsch: "die
|
||||
ausgewählten über die verwaltungsseite auf die website hinzufügen".
|
||||
Eingereichte Nachrichten von stimmen.html landen hier zur Prüfung,
|
||||
werden NIE automatisch veröffentlicht. ===== -->
|
||||
<section class="section-tight" id="testimonials-root" style="display:none;padding-top:0;">
|
||||
<div class="container">
|
||||
<h2 class="text-center" style="margin-bottom:.3rem;">💬 Stimmen verwalten</h2>
|
||||
<p class="small muted text-center" style="max-width:60ch;margin:0 auto 1.4rem;">
|
||||
Einreichungen von der Stimmen-Seite. Erst nach "Freigeben" erscheinen sie öffentlich.
|
||||
</p>
|
||||
<div id="testimonials-liste"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== Supporter-Liste ===== -->
|
||||
<section class="section-tight" id="supp-root" style="display:none;padding-top:0;">
|
||||
<div class="container" id="liste-ansicht">
|
||||
@@ -1018,8 +1032,8 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/main.js?v=20260820j"></script>
|
||||
<script src="assets/js/admin-auth.js?v=20260820j"></script>
|
||||
<script src="assets/js/main.js?v=20260820k"></script>
|
||||
<script src="assets/js/admin-auth.js?v=20260820k"></script>
|
||||
<script>
|
||||
(function () {
|
||||
const API_BASE_URL = "https://postfach.dogfather-universe.com";
|
||||
@@ -1095,6 +1109,10 @@
|
||||
document.getElementById("special-event-root").style.display = "";
|
||||
await ladeUndRenderSpecialEvent();
|
||||
}
|
||||
if (window.AdminAuth.sessionHasPermission(session, "TESTIMONIALS_MANAGE")) {
|
||||
document.getElementById("testimonials-root").style.display = "";
|
||||
await ladeUndRenderTestimonials();
|
||||
}
|
||||
const pollsBox = document.getElementById("box-abstimmungen");
|
||||
if (window.AdminAuth.sessionHasPermission(session, "POLLS_MANAGE")) {
|
||||
pollsBox.style.display = "";
|
||||
@@ -1113,6 +1131,7 @@
|
||||
document.getElementById("supp-root").style.display = "none";
|
||||
document.getElementById("events-root").style.display = "none";
|
||||
document.getElementById("special-event-root").style.display = "none";
|
||||
document.getElementById("testimonials-root").style.display = "none";
|
||||
document.getElementById("box-abstimmungen").style.display = "none";
|
||||
}
|
||||
|
||||
@@ -1477,6 +1496,58 @@
|
||||
});
|
||||
}
|
||||
|
||||
/* ===== "Stimmen" — Verwaltung (20.08.2026) =====
|
||||
Offene Einreichungen zuerst (siehe getTestimonialsAdmin-Sortierung),
|
||||
jede Karte mit Freigeben/Ablehnen/Löschen — je nach aktuellem Status
|
||||
stehen nur die sinnvollen Aktionen zur Verfügung. */
|
||||
const TESTIMONIAL_STATUS_LABELS = { eingereicht: "🟡 Neu", freigegeben: "🟢 Freigegeben", abgelehnt: "⚪ Abgelehnt" };
|
||||
|
||||
function testimonialKarteHtml(t) {
|
||||
return `<div class="card vw-card vw-shine-card" style="margin-bottom:1rem;padding:1.3rem 1.4rem;" data-testimonial-id="${t.id}">
|
||||
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:1rem;flex-wrap:wrap;">
|
||||
<div>
|
||||
<strong>${escapeHtml(t.name)}</strong>${t.tiktokUsername ? ` <span class="small" style="color:var(--text-muted);">@${escapeHtml(t.tiktokUsername)}</span>` : ""}
|
||||
<div class="small" style="color:var(--text-muted);">${TESTIMONIAL_STATUS_LABELS[t.status] || t.status} · eingereicht am ${fmtDatum(t.createdAt)}</div>
|
||||
</div>
|
||||
<div class="btn-row" style="gap:.5rem;">
|
||||
${t.status !== "freigegeben" ? `<button type="button" class="vw-refresh-btn testimonial-freigeben-btn">Freigeben</button>` : ""}
|
||||
${t.status !== "abgelehnt" ? `<button type="button" class="vw-refresh-btn testimonial-ablehnen-btn">Ablehnen</button>` : ""}
|
||||
<button type="button" class="vw-refresh-btn testimonial-loeschen-btn" style="color:#ff8a8a;">Löschen</button>
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin:.8rem 0 0;">${escapeHtml(t.nachricht)}</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
async function ladeUndRenderTestimonials() {
|
||||
const listeEl = document.getElementById("testimonials-liste");
|
||||
const res = await window.AdminAuth.apiCall(API_BASE_URL, "/admin/testimonials/list", {});
|
||||
if (!res.ok) {
|
||||
listeEl.innerHTML = `<p class="vw-leer">Stimmen konnten nicht geladen werden.</p>`;
|
||||
return;
|
||||
}
|
||||
const liste = res.testimonials || [];
|
||||
listeEl.innerHTML = liste.length ? liste.map(testimonialKarteHtml).join("") : `<p class="vw-leer">Noch keine Einreichungen.</p>`;
|
||||
|
||||
const aktion = async (btn, status) => {
|
||||
const id = btn.closest("[data-testimonial-id]").dataset.testimonialId;
|
||||
btn.disabled = true;
|
||||
await window.AdminAuth.apiCall(API_BASE_URL, "/admin/testimonials/status", { id, status });
|
||||
await ladeUndRenderTestimonials();
|
||||
};
|
||||
listeEl.querySelectorAll(".testimonial-freigeben-btn").forEach((btn) => btn.addEventListener("click", () => aktion(btn, "freigegeben")));
|
||||
listeEl.querySelectorAll(".testimonial-ablehnen-btn").forEach((btn) => btn.addEventListener("click", () => aktion(btn, "abgelehnt")));
|
||||
listeEl.querySelectorAll(".testimonial-loeschen-btn").forEach((btn) => {
|
||||
btn.addEventListener("click", async () => {
|
||||
if (!confirm("Diese Einreichung unwiderruflich löschen?")) return;
|
||||
const id = btn.closest("[data-testimonial-id]").dataset.testimonialId;
|
||||
btn.disabled = true;
|
||||
await window.AdminAuth.apiCall(API_BASE_URL, "/admin/testimonials/delete", { id });
|
||||
await ladeUndRenderTestimonials();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Nutzer-Wunsch 04.08.2026 (6. Runde): zwei getrennte Kisten/Formulare
|
||||
// (Dogi + VanVan) statt einem gemeinsamen Feld — beide rufen denselben
|
||||
// Login-Endpunkt auf, die Fehlermeldung erscheint jeweils nur in der
|
||||
|
||||
Reference in New Issue
Block a user