Nandrx ae3692697b Add REST-compliant PUT/DELETE endpoints and invitation validation
- Replace POST /invitation/:id/accept|decline with PUT /invitation/:id
- Add DELETE /invitation/:id (full CRUD: GET, POST, PUT, DELETE)
- Validate invitation recipient exists before creating invitation (404)
- Block self-invitation (400)
- Propagate server error messages to inline card feedback
- Add Loeschen button to invitation cards (demonstrates DELETE in UI)
- Improve empty state with icon and descriptive text
2026-05-31 17:52:25 +02:00

206 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* eventCard.js Erstellt interaktive Event-Karten als DOM-Elemente.
* Jede Karte zeigt Titel, Datum, Veranstaltungsort sowie Speichern- und Einladen-Buttons.
*/
/**
* Erstellt eine vollständige Event-Karte als <article>-Element.
* @param {Object} event - Event-Objekt mit id, name, date, time, venue, category
* @returns {HTMLElement} Fertig aufgebautes Karten-Element
*/
export function createEventCard(event) {
const article = document.createElement("article");
article.className = "event-card";
// --- TITEL ---
const title = document.createElement("h3");
title.className = "event-card__title";
title.textContent = event.name;
// --- DATUM & UHRZEIT formatieren (Schweizer Format: dd.mm.yyyy) ---
const formattedDate = event.date
? new Date(event.date).toLocaleDateString("de-CH", {
day: "2-digit",
month: "2-digit",
year: "numeric"
})
: "Datum unbekannt";
const formattedTime = event.time ? event.time.slice(0, 5) : "";
const dateTime = formattedTime ? `${formattedDate}, ${formattedTime}` : formattedDate;
const date = document.createElement("p");
date.className = "event-card__date";
date.textContent = dateTime;
// --- VERANSTALTUNGSORT ---
const venue = document.createElement("p");
venue.className = "event-card__venue";
venue.textContent = event.venue || "Ort unbekannt";
article.append(title, date, venue);
// --- BUTTON-GRUPPE ---
const buttonContainer = document.createElement("div");
buttonContainer.className = "d-flex gap-2 mt-2 flex-wrap";
// =========================
// SPEICHERN-BUTTON
// Speichert das Event im localStorage, verhindert Duplikate
// =========================
const saveBtn = document.createElement("button");
saveBtn.type = "button";
saveBtn.className = "btn btn-outline-primary btn-sm";
// Prüfen ob bereits gespeichert → Button-Text entsprechend setzen
const alreadySaved = isAlreadySaved(event.id);
saveBtn.textContent = alreadySaved ? "Gespeichert ✓" : "Speichern";
if (alreadySaved) saveBtn.disabled = true;
saveBtn.addEventListener("click", () => {
const saved = JSON.parse(localStorage.getItem("savedEvents") || "[]");
// Duplikat-Schutz
if (saved.find(e => e.id === event.id)) {
saveBtn.textContent = "Gespeichert ✓";
saveBtn.disabled = true;
return;
}
saved.push(event);
localStorage.setItem("savedEvents", JSON.stringify(saved));
// Visuelles Feedback statt alert()
saveBtn.textContent = "Gespeichert ✓";
saveBtn.disabled = true;
saveBtn.classList.replace("btn-outline-primary", "btn-success");
});
// =========================
// EINLADEN-BUTTON
// Zeigt ein Inline-Formular an (kein browser-prompt())
// =========================
const inviteBtn = document.createElement("button");
inviteBtn.type = "button";
inviteBtn.textContent = "Einladen";
inviteBtn.className = "btn btn-primary btn-sm";
inviteBtn.disabled = false;
inviteBtn.addEventListener("click", () => {
if (!window.currentUser) {
showCardMessage(article, "Bitte einloggen um Einladungen zu senden.", "warning");
return;
}
// Formular ein-/ausblenden (Toggle)
const existing = article.querySelector(".invite-form");
if (existing) {
existing.remove();
} else {
article.appendChild(createInviteForm(event, article));
}
});
buttonContainer.append(saveBtn, inviteBtn);
article.appendChild(buttonContainer);
return article;
}
// =========================
// HILFSFUNKTION Einladungsformular
// Erstellt ein kleines Inline-Formular mit Benutzername-Eingabe
// =========================
function createInviteForm(event, card) {
const form = document.createElement("div");
form.className = "invite-form mt-2 d-flex gap-2 align-items-center flex-wrap";
const input = document.createElement("input");
input.type = "text";
input.className = "form-control form-control-sm";
input.placeholder = "Benutzername eingeben";
input.setAttribute("aria-label", "Benutzername für Einladung");
const sendBtn = document.createElement("button");
sendBtn.type = "button";
sendBtn.textContent = "Senden";
sendBtn.className = "btn btn-primary btn-sm";
const cancelBtn = document.createElement("button");
cancelBtn.type = "button";
cancelBtn.textContent = "Abbrechen";
cancelBtn.className = "btn btn-outline-secondary btn-sm";
cancelBtn.addEventListener("click", () => form.remove());
sendBtn.addEventListener("click", async () => {
const toUser = input.value.trim();
if (!toUser) {
input.classList.add("is-invalid");
return;
}
input.classList.remove("is-invalid");
sendBtn.disabled = true;
sendBtn.textContent = "Sende...";
try {
const res = await fetch("http://localhost:3000/api/invitation", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Username": window.currentUser
},
body: JSON.stringify({
toUser,
eventId: event.id,
eventName: event.name
})
});
if (!res.ok) {
// Fehlermeldung vom Server auslesen (z.B. "Nutzer nicht registriert")
const errData = await res.json().catch(() => ({}));
throw new Error(errData.message || `Fehler ${res.status}`);
}
// Formular entfernen und Erfolgsmeldung anzeigen
form.remove();
showCardMessage(card, `Einladung an "${toUser}" gesendet.`, "success");
} catch (err) {
sendBtn.disabled = false;
sendBtn.textContent = "Senden";
showCardMessage(card, err.message || "Fehler beim Senden der Einladung.", "danger");
console.error("Einladungs-Fehler:", err);
}
});
form.append(input, sendBtn, cancelBtn);
return form;
}
// =========================
// HILFSFUNKTION Status-Nachricht in Karte anzeigen
// Zeigt eine kurze Meldung innerhalb der Karte an und blendet sie nach 3s aus
// =========================
function showCardMessage(card, message, type) {
// Alte Nachrichten entfernen
card.querySelectorAll(".card-message").forEach(el => el.remove());
const msg = document.createElement("p");
msg.className = `card-message text-${type} mt-1 mb-0`;
msg.textContent = message;
card.appendChild(msg);
setTimeout(() => msg.remove(), 3000);
}
// =========================
// HILFSFUNKTION Prüft ob Event bereits gespeichert ist
// =========================
function isAlreadySaved(eventId) {
const saved = JSON.parse(localStorage.getItem("savedEvents") || "[]");
return saved.some(e => e.id === eventId);
}