Pop-up auch bei Detailseite hinzufügen. Text angepasst.

This commit is contained in:
Estelle Köhler 2026-04-23 11:30:50 +02:00
parent 244f3b74b1
commit 60b8a5fd3a
2 changed files with 247 additions and 403 deletions

View File

@ -38,6 +38,38 @@
<!-- Page logic: fetch by URL id, compose detail UI, handle gallery lightbox -->
<script src="js/event_detail.js"></script>
<div id="register-confirm-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Ein Platz für dich am Tisch!</h2>
<button type="button" class="modal-close" id="register-modal-close" aria-label="Popup schließen">&times;</button>
</div>
<div class="modal-body">
<p>Schön, dass du dich dazu gesellen möchtest! Da dein:e Gastgeber:in extra für dich einkauft und mit viel Liebe kocht, ist deine Anmeldung ein festes Versprechen. Bitte sag nur zu, wenn du an dem Tag wirklich Zeit hast. So zeigst du echte Wertschätzung für die Mühe und wir lassen niemanden auf vollen Töpfen sitzen.</p>
</div>
<div class="modal-footer" style="display: flex; justify-content: flex-end; gap: 16px; margin-top: 24px;">
<button class="button-primary button--outline" type="button" id="register-modal-cancel">Abbrechen</button>
<button class="button-primary" type="button" id="confirm-register-btn">Ja, ich bin dabei</button>
</div>
</div>
</div>
<div id="unregister-confirm-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Pläne haben sich geändert?</h2>
<button type="button" class="modal-close" id="unregister-modal-close" aria-label="Popup schließen">&times;</button>
</div>
<div class="modal-body">
<p>Schade, dass du nicht dabei sein kannst! Aber manchmal kommt einfach etwas dazwischen. Wenn du dich jetzt abmeldest, gibst du deinen Stuhl am Tisch für jemand anderen aus der Community frei. So hilfst du bei der Planung und ein anderer Feinschmecker freut sich über den freien Platz.</p>
</div>
<div class="modal-footer" style="display: flex; justify-content: flex-end; gap: 16px; margin-top: 24px;">
<button class="button-primary button--outline" type="button" id="unregister-modal-cancel">Abbrechen</button>
<button class="button-primary button-primary-abmelden" type="button" id="confirm-unregister-btn">Ja, abmelden</button>
</div>
</div>
</div>
<!-- Snackbar: Feedback bei An-/Abmeldung -->
<div class="snackbar" id="snackbar"></div>

View File

@ -3,16 +3,13 @@
const CURRENT_USER_KEY = 'socialCookingCurrentUser';
const USERS_STORAGE_KEY = 'socialCookingUsers';
const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations';
// -------------------------------------------------------------
// DOM entry point and shared asset path.
// -------------------------------------------------------------
const detailcontainer = document.getElementById('detail-view');
const locationIconPath = 'assets/icon_location.svg';
const calendarIconPath = 'assets/icon_calendar.svg';
const gastIconPath = 'assets/icon_gast.svg';
const currentUser = getCurrentUser();
// Read event id from query string (detail page deep-link support).
const params = new URLSearchParams(window.location.search);
const eventId = parseInt(params.get('id'));
@ -62,14 +59,10 @@
}
function getUserDisplayName(user) {
if (!user) {
return '';
}
if (!user) return '';
const firstName = String(user.vorname || '').trim();
const lastName = String(user.nachname || '').trim();
const fullName = `${firstName} ${lastName}`.trim();
return (fullName || firstName || String(user.email || '').trim()).trim();
}
@ -85,10 +78,7 @@
Object.entries(registrationMap || {}).forEach(([email, ids]) => {
const isRegisteredForEvent = Array.isArray(ids)
&& ids.map(id => Number(id)).includes(Number(event.id));
if (!isRegisteredForEvent) {
return;
}
if (!isRegisteredForEvent) return;
const user = usersByEmail.get(String(email || '').trim().toLowerCase());
const displayName = getUserDisplayName(user) || String(email || '').trim();
@ -105,19 +95,9 @@
function getParticipantNameForViewer(name, canSeeLastName) {
const rawName = String(name || '').trim();
if (!rawName) {
return '';
}
if (canSeeLastName) {
return rawName;
}
// Bei E-Mail-Fallback nur den lokalen Teil anzeigen.
if (rawName.includes('@')) {
return rawName.split('@')[0].trim() || rawName;
}
if (!rawName) return '';
if (canSeeLastName) return rawName;
if (rawName.includes('@')) return rawName.split('@')[0].trim() || rawName;
return rawName.split(/\s+/)[0];
}
@ -126,153 +106,98 @@
}
function parseEventDateTime(event) {
if (!event?.date) {
return null;
}
if (!event?.date) return null;
const dateValue = String(event.date).trim();
const isoDateMatch = dateValue.match(/^(\d{4})-(\d{2})-(\d{2})$/);
let year;
let month;
let day;
let year, month, day;
if (isoDateMatch) {
year = Number(isoDateMatch[1]);
month = Number(isoDateMatch[2]);
day = Number(isoDateMatch[3]);
} else {
const monthMap = {
JAN: 1,
FEB: 2,
'MÄR': 3,
MRZ: 3,
APR: 4,
MAI: 5,
JUN: 6,
JUL: 7,
AUG: 8,
SEP: 9,
OKT: 10,
NOV: 11,
DEZ: 12
};
const monthMap = { JAN:1, FEB:2, 'MÄR':3, MRZ:3, APR:4, MAI:5, JUN:6, JUL:7, AUG:8, SEP:9, OKT:10, NOV:11, DEZ:12 };
const localizedMatch = dateValue.match(/^(\d{1,2})\.\s*([A-ZÄÖÜ]{3})\.\s*(\d{4})$/);
if (!localizedMatch) {
return null;
}
if (!localizedMatch) return null;
day = Number(localizedMatch[1]);
month = monthMap[localizedMatch[2]];
year = Number(localizedMatch[3]);
if (!month) {
return null;
}
if (!month) return null;
}
const timeMatch = String(event.time || '').match(/(\d{1,2}):(\d{2})/);
const hours = timeMatch ? Number(timeMatch[1]) : 0;
const minutes = timeMatch ? Number(timeMatch[2]) : 0;
return new Date(year, month - 1, day, hours, minutes, 0, 0);
}
function isRegistrationClosedForEvent(event) {
const eventDateTime = parseEventDateTime(event);
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) {
return false;
}
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) return false;
const msUntilStart = eventDateTime.getTime() - Date.now();
const twentyfourHoursInMs = 24 * 60 * 60 * 1000;
return msUntilStart <= twentyfourHoursInMs;
return msUntilStart <= 24 * 60 * 60 * 1000;
}
// Abmeldefrist: 1 Tag (24 h) vor Eventstart.
function getDeregistrationInfo(event) {
const eventDateTime = parseEventDateTime(event);
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) {
return { daysLeft: null, isClosed: false };
}
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) return { daysLeft: null, isClosed: false };
const oneDayMs = 24 * 60 * 60 * 1000;
const deadlineMs = eventDateTime.getTime() - oneDayMs;
const msUntilDeadline = deadlineMs - Date.now();
if (msUntilDeadline <= 0) {
return { daysLeft: 0, isClosed: true };
const msUntilDeadline = (eventDateTime.getTime() - oneDayMs) - Date.now();
if (msUntilDeadline <= 0) return { daysLeft: 0, isClosed: true };
return { daysLeft: Math.ceil(msUntilDeadline / oneDayMs), isClosed: false };
}
const daysLeft = Math.ceil(msUntilDeadline / oneDayMs);
return { daysLeft, isClosed: false };
}
// Adresse ist nur im 24h-Fenster VOR Eventstart sichtbar.
function isAddressVisibleWindow(event) {
const eventDateTime = parseEventDateTime(event);
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) {
return false;
}
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) return false;
const msUntilStart = eventDateTime.getTime() - Date.now();
const twentyfourHoursInMs = 24 * 60 * 60 * 1000;
return msUntilStart >= 0 && msUntilStart <= twentyfourHoursInMs;
return msUntilStart >= 0 && msUntilStart <= 24 * 60 * 60 * 1000;
}
function countRegistrationsForEvent(registrationMap, eventId) {
return Object.values(registrationMap).reduce((count, ids) => {
const hasEvent = Array.isArray(ids)
&& ids.map(id => Number(id)).includes(Number(eventId));
return hasEvent ? count + 1 : count;
}, 0);
}
// Ermittelt, ob das Event vom aktuell eingeloggten Benutzer erstellt wurde.
function isEventOwnedByCurrentUser(event, user) {
if (!event || !user) {
return false;
}
if (!event || !user) return false;
const userEmail = String(user.email || '').trim().toLowerCase();
const hostEmail = String(event.hostEmail || '').trim().toLowerCase();
if (userEmail && hostEmail) {
return userEmail === hostEmail;
}
// Fallback für ältere Datensätze ohne hostEmail.
if (userEmail && hostEmail) return userEmail === hostEmail;
const userFirstName = String(user.vorname || '').trim().toLowerCase();
const hostName = String(event.host?.name || '').trim().toLowerCase();
return Boolean(userFirstName && hostName && userFirstName === hostName);
}
// Prüft, ob der aktuelle Benutzer bereits in der Teilnehmerliste des Events steht.
function isUserListedInEventParticipants(event, user) {
if (!event || !user || !Array.isArray(event.participants)) {
return false;
}
if (!event || !user || !Array.isArray(event.participants)) return false;
const participantSet = new Set(
event.participants
.map(name => String(name || '').trim().toLowerCase())
.filter(Boolean)
event.participants.map(name => String(name || '').trim().toLowerCase()).filter(Boolean)
);
const userFirstName = String(user.vorname || '').trim().toLowerCase();
const userFullName = `${String(user.vorname || '').trim()} ${String(user.nachname || '').trim()}`
.trim()
.toLowerCase();
const userFullName = `${String(user.vorname || '').trim()} ${String(user.nachname || '').trim()}`.trim().toLowerCase();
return Boolean(
(userFirstName && participantSet.has(userFirstName))
|| (userFullName && participantSet.has(userFullName))
);
}
function formatEventDate(dateString) {
if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
const [year, month, day] = dateString.split('-');
return `${Number(day)}. ${['Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'][Number(month)-1]} ${year}`;
}
const labels = { JAN:'Januar', FEB:'Februar', 'MÄR':'März', MRZ:'März', APR:'April', MAI:'Mai', JUN:'Juni', JUL:'Juli', AUG:'August', SEP:'September', OKT:'Oktober', NOV:'November', DEZ:'Dezember' };
const match = dateString.match(/^(\d{1,2})\.\s*([A-ZÄÖÜ]{3})\.\s*(\d{4})$/);
if (!match) return dateString;
const monthLabel = labels[match[2]];
return monthLabel ? `${Number(match[1])}. ${monthLabel} ${match[3]}` : dateString;
}
function formatEventTime(timeString) {
return timeString.replace('UHR', 'Uhr').trim();
}
function getDietLabel(diet) {
const labels = { FLEISCH:'Fleisch', FISCH:'Fisch', VEGGIE:'Vegetarisch', VEGAN:'Vegan' };
return labels[diet] || diet;
}
// Fetch data source and resolve the matching event record.
try {
const response = await fetch('data/events.json');
@ -289,62 +214,11 @@
console.error("Fehler beim Laden der Details:", error);
}
// Format localized date token into full readable date.
function formatEventDate(dateString) {
const labels = {
JAN: 'Januar',
FEB: 'Februar',
'MÄR': 'März',
MRZ: 'März',
APR: 'April',
MAI: 'Mai',
JUN: 'Juni',
JUL: 'Juli',
AUG: 'August',
SEP: 'September',
OKT: 'Oktober',
NOV: 'November',
DEZ: 'Dezember'
};
const match = dateString.match(/^(\d{1,2})\.\s*([A-ZÄÖÜ]{3})\.\s*(\d{4})$/);
if (!match) {
return dateString;
}
const day = Number(match[1]);
const monthLabel = labels[match[2]];
const year = match[3];
return monthLabel ? `${day}. ${monthLabel} ${year}` : dateString;
}
// Normalize time casing for UI consistency.
function formatEventTime(timeString) {
return timeString.replace('UHR', 'Uhr').trim();
}
// Map diet keys to readable labels while keeping unknown values untouched.
function getDietLabel(diet) {
const labels = {
FLEISCH: 'Fleisch',
FISCH: 'Fisch',
VEGGIE: 'Vegetarisch',
VEGAN: 'Vegan'
};
return labels[diet] || diet;
}
// Compose and inject the full detail UI for a single event.
function renderDetailPage(event) {
// Core display values and resilient fallbacks for optional data fields.
const displayDate = formatEventDate(event.date);
const displayTime = formatEventTime(event.time);
const dietLabel = getDietLabel(event.diet);
const eventCategory = event.category || 'EVENT';
const hostName = event.host?.name || 'Host';
const hostInitial = (event.host?.initial || hostName.charAt(0) || 'H').charAt(0).toUpperCase();
const hostMessage = Array.isArray(event.hostMessage) && event.hostMessage.length > 0
? event.hostMessage
: ['Der Host hat für dieses Event noch keine Nachricht hinterlegt.'];
@ -352,28 +226,23 @@
? event.menu
: ['Menü wird in Kürze bekannt gegeben.'];
const specifications = Array.isArray(event.specifications) && event.specifications.length > 0
? event.specifications
: [];
? event.specifications : [];
const registrationMap = getRegistrationMap();
const participants = getResolvedParticipants(event, registrationMap);
const isOwnEvent = isEventOwnedByCurrentUser(event, currentUser);
const participantNamesForView = participants
.map(name => getParticipantNameForViewer(name, isOwnEvent))
.filter(Boolean);
const galleryImages = Array.isArray(event.gallery)
? event.gallery.filter(Boolean)
: [];
const galleryImages = Array.isArray(event.gallery) ? event.gallery.filter(Boolean) : [];
const galleryMarkup = galleryImages.length > 0
? `
<div class="detail-gallery detail-gallery-large">
? `<div class="detail-gallery detail-gallery-large">
${galleryImages.slice(0, 9).map((img, index) => `
<button class="detail-gallery-item" type="button" aria-label="Bild ${index + 1} gross anzeigen" data-fullsrc="${img}">
<img src="${img}" alt="${event.title} Bild ${index + 1}" class="detail-gallery-image">
</button>
`).join('')}
</div>
`
: '';
</div>` : '';
const visibleParticipants = participantNamesForView.slice(0, 6);
const remainingParticipants = Math.max(0, participantNamesForView.length - visibleParticipants.length);
const totalGuests = Number.isFinite(event.spots) ? event.spots : 0;
@ -383,72 +252,43 @@
const isRegistrationClosed = isRegistrationClosedForEvent(event);
const deregInfo = getDeregistrationInfo(event);
const userRegistrations = currentUser?.email && Array.isArray(registrationMap[currentUser.email])
? registrationMap[currentUser.email].map(id => Number(id))
: [];
? registrationMap[currentUser.email].map(id => Number(id)) : [];
const isRegistered = userRegistrations.includes(Number(event.id));
const isListedParticipant = isUserListedInEventParticipants(event, currentUser);
const hasAddressAccess = isRegistered || isListedParticipant;
const actionButtonLabel = isOwnEvent
? 'Dein Event!'
: !currentUser
? 'Einloggen'
: isRegistered
? (deregInfo.isClosed ? 'Abmeldung geschlossen' : 'Abmelden')
: isRegistrationClosed
? 'Anmeldung geschlossen'
const actionButtonLabel = isOwnEvent ? 'Dein Event!'
: !currentUser ? 'Einloggen'
: isRegistered ? (deregInfo.isClosed ? 'Abmeldung geschlossen' : 'Abmelden')
: isRegistrationClosed ? 'Anmeldung geschlossen'
: 'Anmelden';
const actionButtonDisabled = isOwnEvent
|| (!isRegistered && (isFull || isRegistrationClosed))
|| (isRegistered && deregInfo.isClosed);
const actionButtonVariantClass = isOwnEvent
? ' button-primary-eigener-event'
: isRegistered
? ' button-primary-abmelden '
: isRegistrationClosed
? ' button-primary '
const actionButtonVariantClass = isOwnEvent ? ' button-primary-eigener-event'
: isRegistered ? ' button-primary-abmelden '
: ' button-primary ';
const shouldRevealAddress = Boolean(event.address) && isAddressVisibleWindow(event) && hasAddressAccess;
const addressPanelMarkup = shouldRevealAddress
? `
<article class="detail-panel">
<h2 class="detail-section-title">Adresse</h2>
<p>${event.address}</p>
</article>
`
: `
<article class="detail-panel">
<h2 class="detail-section-title">Adresse</h2>
<p>Vielen Dank für die Anmeldung! Die Adresse für diesen Event wird 24 Stunden vorher genau hier sichtbar sein.</p>
</article>
`;
? `<article class="detail-panel"><h2 class="detail-section-title">Adresse</h2><p>${event.address}</p></article>`
: `<article class="detail-panel"><h2 class="detail-section-title">Adresse</h2><p>Vielen Dank für die Anmeldung! Die Adresse für diesen Event wird 24 Stunden vorher genau hier sichtbar sein.</p></article>`;
const detailChips = [
`<span class="event-tag">${eventCategory}</span>`,
...event.diet.split(', ').filter(d => d.trim() && d !== 'Keine Angabe').map(d => `<span class="event-tag">${getDietLabel(d.trim())}</span>`),
...specifications.map(item => `<span class="event-tag">${item}</span>`)
].join('');
// Render complete detail page layout including:
// hero metadata, host card, menu, participants, gallery and sticky action bar.
detailcontainer.innerHTML = `
<section class="detail-hero">
<div class="detail-top-row">
<span class="event-location">
<img src="${locationIconPath}" class="icon" alt="">${event.location}
</span>
<span class="event-date-time">
<img src="${calendarIconPath}" class="icon" alt=""> ${displayDate} | ${displayTime}
</span>
<span class="event-date-time">
<img src="${gastIconPath}" class="icon" alt="">${confirmedGuests}/${totalGuests}
</span>
<span class="event-location"><img src="${locationIconPath}" class="icon" alt="">${event.location}</span>
<span class="event-date-time"><img src="${calendarIconPath}" class="icon" alt=""> ${displayDate} | ${displayTime}</span>
<span class="event-date-time"><img src="${gastIconPath}" class="icon" alt="">${confirmedGuests}/${totalGuests}</span>
</div>
<h1 class="detail-title">${event.title}</h1>
<div class="event-meta-row detail-chip-row">
${detailChips}
</div>
<div class="event-meta-row detail-chip-row">${detailChips}</div>
</section>
<section class="detail-content-grid">
@ -488,55 +328,30 @@
${addressPanelMarkup}
</div>
${galleryMarkup}
</section>
<section class="detail-action-bar">
<div class="detail-action-summary">
<small class="detail-action-meta">
<span class="event-location detail-action-location">
<img src="${locationIconPath}" alt="">
${event.location}
</span>
<span class="event-date-time detail-action-location">
<img src="${calendarIconPath}" alt=""> ${displayDate} | ${displayTime}
</span>
<span class="event-gast detail-action-location">
<img src="${gastIconPath}" alt="">
${confirmedGuests}/${totalGuests}
</span>
<span class="event-location detail-action-location"><img src="${locationIconPath}" alt="">${event.location}</span>
<span class="event-date-time detail-action-location"><img src="${calendarIconPath}" alt=""> ${displayDate} | ${displayTime}</span>
<span class="event-gast detail-action-location"><img src="${gastIconPath}" alt="">${confirmedGuests}/${totalGuests}</span>
</small>
<strong>${event.title}</strong>
</div>
<div class="detail-action-btn-wrap">
${isFull ? `
<button
class="detail-primary-btn detail-spots-pill-full"
type="button"
disabled>
Ausgebucht
</button>
<button class="detail-primary-btn detail-spots-pill-full" type="button" disabled>Ausgebucht</button>
` : `
<button
class="detail-primary-btn${actionButtonVariantClass}"
type="button"
data-register-button
${actionButtonDisabled ? 'disabled' : ''}>
<button class="detail-primary-btn${actionButtonVariantClass}" type="button" data-register-button ${actionButtonDisabled ? 'disabled' : ''}>
${actionButtonLabel}
</button>
`}
${isRegistered && deregInfo.daysLeft !== null ? `
<small class="detail-dereg-hint${deregInfo.isClosed ? ' detail-dereg-hint--closed' : ''}">
${deregInfo.isClosed
? 'Abmeldefrist abgelaufen'
: deregInfo.daysLeft === 1
? 'Noch 1 Tag zur Abmeldung'
${deregInfo.isClosed ? 'Abmeldefrist abgelaufen'
: deregInfo.daysLeft === 1 ? 'Noch 1 Tag zur Abmeldung'
: `Noch ${deregInfo.daysLeft} Tage zur Abmeldung`}
</small>
` : ''}
@ -550,48 +365,50 @@
<img class="detail-lightbox-image" src="" alt="Grossansicht Eventbild">
</figure>
</div>
</div>
`;
// ---------------------------------------------------------
// Lightbox behavior for gallery images:
// open on image click, close via backdrop, close button or ESC.
// ---------------------------------------------------------
// DOM references after render
const lightbox = detailcontainer.querySelector('.detail-lightbox');
const lightboxImage = detailcontainer.querySelector('.detail-lightbox-image');
const lightboxClose = detailcontainer.querySelector('.detail-lightbox-close');
const galleryButtons = detailcontainer.querySelectorAll('.detail-gallery-item');
const registerButton = detailcontainer.querySelector('[data-register-button]');
// Harte Absicherung: Eigene Events sind auf der Detailseite immer deaktiviert.
// Eigene Events immer deaktiviert
if (registerButton && isOwnEvent) {
registerButton.disabled = true;
registerButton.textContent = 'Dein Event!';
registerButton.setAttribute('aria-disabled', 'true');
}
// Anmeldung toggeln und im lokalen Registrierungs-Store persistieren.
// Anmeldung / Abmeldung mit Bestätigungs-Modal
if (registerButton) {
registerButton.addEventListener('click', () => {
if (isOwnEvent) {
return;
}
if (isOwnEvent) return;
if (!currentUser || !currentUser.email) {
window.location.href = 'login.html';
return;
}
const nextRegistrationMap = getRegistrationMap();
const currentList = Array.isArray(nextRegistrationMap[currentUser.email])
? nextRegistrationMap[currentUser.email].map(id => Number(id))
: [];
const registrationSet = new Set(currentList);
const alreadyRegistered = (() => {
const map = getRegistrationMap();
const ids = Array.isArray(map[currentUser.email])
? map[currentUser.email].map(id => Number(id)) : [];
return ids.includes(Number(event.id));
})();
if (registrationSet.has(Number(event.id))) {
registrationSet.delete(Number(event.id));
if (alreadyRegistered) {
const modal = document.getElementById('unregister-confirm-modal');
if (modal) modal.classList.add('show');
// Snackbar: Feedback bei Abmeldung.
document.getElementById('confirm-unregister-btn').onclick = () => {
modal.classList.remove('show');
const map = getRegistrationMap();
const ids = new Set((map[currentUser.email] || []).map(id => Number(id)));
ids.delete(Number(event.id));
map[currentUser.email] = Array.from(ids);
setRegistrationMap(map);
const snackbar = document.getElementById('snackbar');
if (snackbar) {
snackbar.textContent = 'Du wurdest erfolgreich abgemeldet.';
@ -601,27 +418,41 @@
setTimeout(() => snackbar.classList.remove('snackbar--danger'), 400);
}, 3000);
}
} else if (!isFull && !isRegistrationClosed) {
registrationSet.add(Number(event.id));
renderDetailPage(event);
};
// Snackbar: Feedback bei Anmeldung.
document.getElementById('unregister-modal-close').onclick = () => modal.classList.remove('show');
document.getElementById('unregister-modal-cancel').onclick = () => modal.classList.remove('show');
modal.addEventListener('click', e => { if (e.target === modal) modal.classList.remove('show'); });
} else if (!isFull && !isRegistrationClosed) {
const modal = document.getElementById('register-confirm-modal');
if (modal) modal.classList.add('show');
document.getElementById('confirm-register-btn').onclick = () => {
modal.classList.remove('show');
const map = getRegistrationMap();
const ids = new Set((map[currentUser.email] || []).map(id => Number(id)));
ids.add(Number(event.id));
map[currentUser.email] = Array.from(ids);
setRegistrationMap(map);
const snackbar = document.getElementById('snackbar');
if (snackbar) {
snackbar.textContent = 'Du wurdest erfolgreich angemeldet.';
snackbar.classList.add('snackbar--visible');
setTimeout(() => snackbar.classList.remove('snackbar--visible'), 3000);
}
}
nextRegistrationMap[currentUser.email] = Array.from(registrationSet);
setRegistrationMap(nextRegistrationMap);
// Re-Render aktualisiert Buttonzustand und CTA ohne Seitenreload.
renderDetailPage(event);
};
document.getElementById('register-modal-close').onclick = () => modal.classList.remove('show');
document.getElementById('register-modal-cancel').onclick = () => modal.classList.remove('show');
modal.addEventListener('click', e => { if (e.target === modal) modal.classList.remove('show'); });
}
});
}
// "Alle ansehen": Teilnehmerliste aufklappen / zuklappen.
// "Alle ansehen": Teilnehmerliste aufklappen / zuklappen
const showAllBtn = detailcontainer.querySelector('[data-show-all-participants]');
const avatarRow = detailcontainer.querySelector('[data-participants-row]');
const fullList = detailcontainer.querySelector('[data-participants-full]');
@ -635,32 +466,24 @@
});
}
// Central close helper to keep all close paths consistent.
// Lightbox
function closeLightbox() {
if (!lightbox) {
return;
}
if (!lightbox) return;
lightbox.classList.remove('is-open');
lightbox.setAttribute('aria-hidden', 'true');
}
if (lightbox && lightboxImage) {
// Open with selected image source.
galleryButtons.forEach(button => {
button.addEventListener('click', () => {
const imageSrc = button.getAttribute('data-fullsrc');
if (!imageSrc) {
return;
}
if (!imageSrc) return;
lightboxImage.src = imageSrc;
lightbox.classList.add('is-open');
lightbox.setAttribute('aria-hidden', 'false');
});
});
// Close when user clicks on backdrop.
lightbox.addEventListener('click', event => {
const target = event.target;
if (target instanceof HTMLElement && target.hasAttribute('data-close-lightbox')) {
@ -668,21 +491,10 @@
}
});
// Close via dedicated icon/button.
lightboxClose?.addEventListener('click', closeLightbox);
// Close with keyboard for accessibility.
document.addEventListener('keydown', event => {
if (event.key === 'Escape') {
closeLightbox();
}
});
}
// Back button navigation: returns to event overview page.
if (backButton) {
backButton.addEventListener('click', () => {
window.location.href = 'event_overview.html';
if (event.key === 'Escape') closeLightbox();
});
}
}