diff --git a/css/event_overview.css b/css/event_overview.css index 0c3e203..5f99e94 100644 --- a/css/event_overview.css +++ b/css/event_overview.css @@ -266,7 +266,6 @@ } .btn-primary { - background: var(--olive); color: #fffde8; border: none; border-radius: var(--radius-pill); @@ -275,10 +274,44 @@ line-height: 1.3; cursor: pointer; white-space: nowrap; + transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease; } -.btn-primary:hover { - filter: brightness(0.95); +.btn-primary-register { + background: var(--olive); +} + +.btn-primary-register:hover, +.btn-primary-register:focus-visible { + background: #575704; + transform: translateY(-1px); + box-shadow: 0 4px 10px rgba(107, 107, 5, 0.28); +} + +.btn-primary-register:active { + transform: translateY(0); + box-shadow: 0 2px 6px rgba(107, 107, 5, 0.25); +} + +.btn-primary-danger { + background: var(--tomato); +} + +.btn-primary-danger:hover, +.btn-primary-danger:focus-visible { + background: var(--tomato-dark); + transform: translateY(-1px); + box-shadow: 0 4px 10px rgba(188, 74, 52, 0.28); +} + +.btn-primary-danger:active { + transform: translateY(0); + box-shadow: 0 2px 6px rgba(188, 74, 52, 0.25); +} + +.btn-primary:disabled { + opacity: 0.6; + cursor: not-allowed; } .btn-primary-own, @@ -688,12 +721,42 @@ } .detail-primary-btn { - border: 2px solid var(--tomato); border-radius: var(--radius-pill); - background: var(--tomato); color: var(--white); padding: 10px 22px; cursor: pointer; + transition: background-color 0.2s ease, border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease; +} + +.detail-primary-btn-register { + border: 2px solid var(--olive); + background: var(--olive); +} + +.detail-primary-btn-register:not(:disabled):hover, +.detail-primary-btn-register:not(:disabled):focus-visible { + background: #575704; + border-color: #575704; + transform: translateY(-1px); + box-shadow: 0 4px 10px rgba(107, 107, 5, 0.28); +} + +.detail-primary-btn-danger { + border: 2px solid var(--tomato); + background: var(--tomato); +} + +.detail-primary-btn-danger:not(:disabled):hover, +.detail-primary-btn-danger:not(:disabled):focus-visible { + background: var(--tomato-dark); + border-color: var(--tomato-dark); + transform: translateY(-1px); + box-shadow: 0 4px 10px rgba(188, 74, 52, 0.28); +} + +.detail-primary-btn:not(:disabled):active { + transform: translateY(0); + box-shadow: 0 2px 6px rgba(102, 52, 13, 0.22); } .detail-primary-btn:disabled { diff --git a/css/my_profil.css b/css/my_profil.css index 825504a..28c9e15 100644 --- a/css/my_profil.css +++ b/css/my_profil.css @@ -44,6 +44,38 @@ gap: var(--space-4); } +.profile-tabs { + display: inline-flex; + flex-wrap: wrap; + gap: var(--space-2); +} + +.profile-tab { + border: 2px solid var(--olive); + border-radius: var(--radius-md); + background: var(--butter); + color: var(--black); + padding: 0.45rem 1rem; + min-height: 2.5rem; + font-family: "Jost", sans-serif; + font-size: 1rem; + font-weight: 500; + letter-spacing: var(--ls-ui); + cursor: pointer; + transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease; +} + +.profile-tab:hover, +.profile-tab:focus-visible { + background: #faf8e8; +} + +.profile-tab.is-active { + border-color: transparent; + background: var(--olive); + color: var(--white); +} + /* Konsistentes Karten-Layout fuer alle Profilsektionen. */ .profile-panel { background: rgba(255, 255, 255, 0.88); @@ -92,6 +124,16 @@ gap: var(--space-3); } +.profile-event-card-clickable { + cursor: pointer; + transition: box-shadow 0.2s ease, transform 0.2s ease; +} + +.profile-event-card-clickable:hover { + box-shadow: 0 6px 16px rgba(102, 52, 13, 0.14); + transform: translateY(-1px); +} + .profile-event-title { margin: 0; color: var(--black); @@ -106,6 +148,31 @@ color: var(--olive); } +.profile-event-address-block { + margin-top: 0.55rem; + padding: 0.6rem 0.75rem; + border-radius: var(--radius-sm); + border-left: 4px solid var(--tomato); + background: rgba(232, 237, 209, 0.65); +} + +.profile-event-address-label { + margin: 0; + color: var(--olive); + font-size: 0.72rem; + font-weight: 700; + letter-spacing: var(--ls-label); + text-transform: uppercase; +} + +.profile-event-address { + margin: 0.2rem 0 0; + font-size: 0.95rem; + color: var(--black); + font-weight: 600; + line-height: 1.35; +} + .profile-event-link { flex-shrink: 0; color: var(--blue); @@ -135,11 +202,44 @@ font-size: 0.95rem; font-weight: 500; cursor: pointer; + transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease; +} + +.profile-cancel-btn { + border: none; + border-radius: var(--radius-md); + background: var(--tomato); + color: var(--butter-light); + padding: 0.45rem 0.95rem; + font-family: "Jost", sans-serif; + font-size: 0.95rem; + font-weight: 500; + cursor: pointer; + transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease; +} + +.profile-cancel-btn:hover, +.profile-cancel-btn:focus-visible { + background: var(--tomato-dark); + transform: translateY(-1px); + box-shadow: 0 4px 10px rgba(188, 74, 52, 0.28); +} + +.profile-cancel-btn:active { + transform: translateY(0); + box-shadow: 0 2px 6px rgba(188, 74, 52, 0.25); } .profile-unregister-btn:hover, .profile-unregister-btn:focus-visible { background: var(--tomato-dark); + transform: translateY(-1px); + box-shadow: 0 4px 10px rgba(188, 74, 52, 0.28); +} + +.profile-unregister-btn:active { + transform: translateY(0); + box-shadow: 0 2px 6px rgba(188, 74, 52, 0.25); } .profile-empty { @@ -147,6 +247,35 @@ color: var(--black); } +.profile-empty-state { + text-align: center; + padding: 2.4rem 1.3rem; + border: 2px solid var(--olive-light); + border-radius: var(--radius-lg); + background: rgba(255, 255, 255, 0.92); + box-shadow: 0 3px 12px rgba(102, 52, 13, 0.08); +} + +.profile-empty-kicker { + margin: 0 0 0.5rem; + color: var(--olive); + font-size: 0.8rem; + font-weight: 600; + letter-spacing: var(--ls-label); + text-transform: uppercase; +} + +.profile-empty-state h3 { + margin: 0; + font-size: 1.5rem; + color: var(--brown); +} + +.profile-empty-state p { + margin: 0.65rem auto 1rem; + max-width: 36rem; +} + .form-grid { display: grid; grid-template-columns: 1fr 1fr; diff --git a/data/events.json b/data/events.json index c5d1502..7f84219 100644 --- a/data/events.json +++ b/data/events.json @@ -3,8 +3,9 @@ "id": 1, "title": "Italienische Tavolata", "location": "Luzern", - "date": "19. MÄR. 2026", - "time": "18:30 UHR", + "address": "Pilatusstrasse 18, 6003 Luzern", + "date": "11. APR. 2026", + "time": "3:30 UHR", "category": "DINNER", "diet": "VEGGIE", "spots": 6, @@ -43,6 +44,7 @@ "id": 2, "title": "Noche Peruana", "location": "Chur", + "address": "Obere Gasse 41, 7000 Chur", "date": "11. APR. 2026", "time": "19:00 UHR", "category": "DINNER", @@ -84,6 +86,7 @@ "id": 3, "title": "Japanese Delight", "location": "ZÜRICH", + "address": "Limmatquai 92, 8001 Zürich", "date": "02. MAI. 2026", "time": "12:30 UHR", "category": "LUNCH", diff --git a/js/event_create.js b/js/event_create.js index e72d82d..d3cef97 100644 --- a/js/event_create.js +++ b/js/event_create.js @@ -429,7 +429,8 @@ function buildStoredEvent() { ? [] : getCheckboxValues("allergies").split(", ").filter(Boolean), allergiesNote: form.elements.allergiesOther.value.trim(), - participants: [usernameElement.textContent.trim() || "Host"], + // Host wird separat gefuehrt und nicht als angemeldeter Gast gezaehlt. + participants: [], gallery: [], createdAt: new Date().toISOString(), source: "local" diff --git a/js/event_detail.js b/js/event_detail.js index 53bb7e2..2ca06ac 100644 --- a/js/event_detail.js +++ b/js/event_detail.js @@ -52,6 +52,80 @@ document.addEventListener('DOMContentLoaded', async () => { localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(registrationMap)); } + function parseEventDateTime(event) { + 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; + + 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 localizedMatch = dateValue.match(/^(\d{1,2})\.\s*([A-ZÄÖÜ]{3})\.\s*(\d{4})$/); + + if (!localizedMatch) { + return null; + } + + day = Number(localizedMatch[1]); + month = monthMap[localizedMatch[2]]; + year = Number(localizedMatch[3]); + + 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; + } + + const msUntilStart = eventDateTime.getTime() - Date.now(); + const twelveHoursInMs = 12 * 60 * 60 * 1000; + + return msUntilStart <= twelveHoursInMs; + } + + 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) { @@ -71,6 +145,29 @@ document.addEventListener('DOMContentLoaded', async () => { return Boolean(userFirstName && hostName && userFirstName === hostName); } + // Prueft, ob der aktuelle Benutzer bereits in der Teilnehmerliste des Events steht. + function isUserListedInEventParticipants(event, user) { + if (!event || !user || !Array.isArray(event.participants)) { + return false; + } + + const participantSet = new Set( + 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(); + + return Boolean( + (userFirstName && participantSet.has(userFirstName)) + || (userFullName && participantSet.has(userFullName)) + ); + } + // Fetch data source and resolve the matching event record. try { const response = await fetch('data/events.json'); @@ -157,19 +254,47 @@ document.addEventListener('DOMContentLoaded', async () => { ? event.gallery : [event.image, event.image, event.image]; const visibleParticipants = participants.slice(0, 6); - const remainingParticipants = Math.max(0, participants.length - visibleParticipants.length); + const registrationMap = getRegistrationMap(); + const extraRegistrations = countRegistrationsForEvent(registrationMap, event.id); + const remainingParticipants = Math.max(0, participants.length + extraRegistrations - visibleParticipants.length); const totalGuests = Number.isFinite(event.spots) ? event.spots : 0; - const confirmedGuests = participants.length; + const confirmedGuests = participants.length + extraRegistrations; const freePlaces = Math.max(0, totalGuests - confirmedGuests); const isFull = freePlaces === 0; + const isRegistrationClosed = isRegistrationClosedForEvent(event); const isOwnEvent = isEventOwnedByCurrentUser(event, currentUser); - const registrationMap = getRegistrationMap(); const userRegistrations = currentUser?.email && Array.isArray(registrationMap[currentUser.email]) ? registrationMap[currentUser.email].map(id => Number(id)) : []; const isRegistered = userRegistrations.includes(Number(event.id)); - const actionButtonLabel = isOwnEvent ? 'Dein Event' : !currentUser ? 'Einloggen' : isRegistered ? 'Abmelden' : 'Anmelden'; - const actionButtonDisabled = isOwnEvent || (!isRegistered && isFull); + const isListedParticipant = isUserListedInEventParticipants(event, currentUser); + const hasAddressAccess = isRegistered || isListedParticipant; + const actionButtonLabel = isOwnEvent + ? 'Dein Event!' + : !currentUser + ? 'Einloggen' + : isRegistered + ? 'Abmelden' + : isRegistrationClosed + ? 'Anmeldung geschlossen' + : 'Anmelden'; + const actionButtonDisabled = isOwnEvent || (!isRegistered && (isFull || isRegistrationClosed)); + const actionButtonVariantClass = isOwnEvent + ? ' detail-primary-btn-own' + : isRegistered + ? ' detail-primary-btn-danger' + : isRegistrationClosed + ? ' detail-primary-btn-danger' + : ' detail-primary-btn-register'; + const shouldRevealAddress = Boolean(event.address) && isRegistrationClosed && hasAddressAccess; + const addressPanelMarkup = shouldRevealAddress + ? ` +
+

Adresse

+

${event.address}

+
+ ` + : ''; const detailChips = [ `${eventCategory}`, `${dietLabel}`, @@ -227,6 +352,8 @@ document.addEventListener('DOMContentLoaded', async () => { ${remainingParticipants > 0 ? `+${remainingParticipants}` : ''} + + ${addressPanelMarkup} @@ -280,6 +407,13 @@ document.addEventListener('DOMContentLoaded', async () => { const galleryButtons = detailContainer.querySelectorAll('.detail-gallery-item'); const registerButton = detailContainer.querySelector('[data-register-button]'); + // Harte Absicherung: Eigene Events sind auf der Detailseite 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. if (registerButton) { registerButton.addEventListener('click', () => { @@ -300,7 +434,7 @@ document.addEventListener('DOMContentLoaded', async () => { if (registrationSet.has(Number(event.id))) { registrationSet.delete(Number(event.id)); - } else if (!isFull) { + } else if (!isFull && !isRegistrationClosed) { registrationSet.add(Number(event.id)); } diff --git a/js/event_overview.js b/js/event_overview.js index 15bf312..723a753 100644 --- a/js/event_overview.js +++ b/js/event_overview.js @@ -1,6 +1,7 @@ document.addEventListener('DOMContentLoaded', () => { const EVENTS_STORAGE_KEY = 'socialCookingEvents'; const CURRENT_USER_KEY = 'socialCookingCurrentUser'; + const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations'; // ------------------------------------------------------------- // DOM references used throughout the page lifecycle. // ------------------------------------------------------------- @@ -55,6 +56,20 @@ document.addEventListener('DOMContentLoaded', () => { } } + function getRegistrationMap() { + try { + const stored = localStorage.getItem(REGISTRATION_STORAGE_KEY); + return stored ? JSON.parse(stored) : {}; + } catch (error) { + console.error('Anmeldedaten konnten nicht gelesen werden.', error); + return {}; + } + } + + function setRegistrationMap(registrationMap) { + localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(registrationMap)); + } + // ------------------------------------------------------------- // Initial data bootstrap: // 1) fetch JSON, @@ -183,6 +198,83 @@ document.addEventListener('DOMContentLoaded', () => { : `${timeString} Uhr`; } + // Baut aus Eventdatum/-zeit ein Date-Objekt fuer Fristlogik und Vergleiche. + function parseEventDateTime(event) { + 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; + + 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 localizedMatch = dateValue.match(/^(\d{1,2})\.\s*([A-ZÄÖÜ]{3})\.\s*(\d{4})$/); + + if (!localizedMatch) { + return null; + } + + day = Number(localizedMatch[1]); + month = monthMap[localizedMatch[2]]; + year = Number(localizedMatch[3]); + + 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); + } + + // Zaehlt eindeutige Registrierungen eines Events ueber alle Benutzer. + 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); + } + + // Schliesst neue Anmeldungen ab 12h vor Start (inkl. bereits gestarteter Events). + function isRegistrationClosedForEvent(event) { + const eventDateTime = parseEventDateTime(event); + if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) { + return false; + } + + const msUntilStart = eventDateTime.getTime() - Date.now(); + const twelveHoursInMs = 12 * 60 * 60 * 1000; + + return msUntilStart <= twelveHoursInMs; + } + // Safely verify whether a value exists in the given select element. function hasOption(selectElement, value) { return Array.from(selectElement.options).some(option => option.value === value); @@ -202,6 +294,11 @@ document.addEventListener('DOMContentLoaded', () => { }); const filtered = allEvents.filter(event => { + // Lokal erstellte Events werden nicht in der allgemeinen Event-Uebersicht angezeigt. + if (event.source === 'local') { + return false; + } + const categoryMatch = activeCategory === 'ALLE' || event.category === activeCategory; const locationMatch = selectedLocation === 'ALLE_ORTE' || event.location === selectedLocation; const eventDateIso = parseEventDateToIso(event.date); @@ -222,6 +319,10 @@ document.addEventListener('DOMContentLoaded', () => { // - or event cards with status and metadata. function renderEvents(events) { eventGrid.innerHTML = ''; + const registrationMap = getRegistrationMap(); + const userRegistrationSet = currentUser?.email && Array.isArray(registrationMap[currentUser.email]) + ? new Set(registrationMap[currentUser.email].map(id => Number(id))) + : new Set(); if (events.length === 0) { eventGrid.innerHTML = ` @@ -242,20 +343,28 @@ document.addEventListener('DOMContentLoaded', () => { const card = document.createElement('article'); card.className = 'event-card'; card.style.cursor = 'pointer'; - card.onclick = () => { + card.addEventListener('click', clickedEvent => { + if (clickedEvent.target instanceof HTMLElement && clickedEvent.target.closest('button')) { + return; + } + window.location.href = `event_detail.html?id=${event.id}`; - }; + }); const displayDate = formatEventDate(event.date); const displayTime = formatEventTime(event.time); // Capacity logic: // spots = total capacity, participants.length = booked seats. - const bookedSeats = event.participants ? event.participants.length : 0; + const baseParticipants = Array.isArray(event.participants) ? event.participants.length : 0; + const extraRegistrations = countRegistrationsForEvent(registrationMap, event.id); + const bookedSeats = baseParticipants + extraRegistrations; const totalCapacity = event.spots; const freePlaces = Math.max(0, totalCapacity - bookedSeats); const isFull = freePlaces === 0; const isOwnEvent = isEventOwnedByCurrentUser(event, currentUser); + const isRegistered = userRegistrationSet.has(Number(event.id)); + const isRegistrationClosed = isRegistrationClosedForEvent(event); // Build optional specification chips only when data exists. const specsChips = event.specifications && event.specifications.length > 0 @@ -263,10 +372,16 @@ document.addEventListener('DOMContentLoaded', () => { : ''; const actionMarkup = isOwnEvent - ? '' - : isFull - ? '' - : ''; + ? '' + : isRegistered + ? '' + : isRegistrationClosed + ? '' + : isFull + ? '' + : !currentUser + ? '' + : ''; card.innerHTML = `
@@ -290,6 +405,50 @@ document.addEventListener('DOMContentLoaded', () => {
`; + const actionButton = card.querySelector('[data-registration-action]'); + if (actionButton) { + actionButton.addEventListener('click', clickEvent => { + clickEvent.stopPropagation(); + + const action = actionButton.getAttribute('data-registration-action'); + if (action === 'own') { + return; + } + + if (action === 'closed') { + return; + } + + if (action === 'login') { + window.location.href = 'login.html'; + return; + } + + if (!currentUser?.email) { + window.location.href = 'login.html'; + return; + } + + const nextRegistrationMap = getRegistrationMap(); + const currentIds = Array.isArray(nextRegistrationMap[currentUser.email]) + ? nextRegistrationMap[currentUser.email].map(id => Number(id)) + : []; + const idSet = new Set(currentIds); + + if (action === 'unregister') { + idSet.delete(Number(event.id)); + } + + if (action === 'register' && !isFull && !isRegistrationClosed) { + idSet.add(Number(event.id)); + } + + nextRegistrationMap[currentUser.email] = Array.from(idSet); + setRegistrationMap(nextRegistrationMap); + applyFilters(); + }); + } + eventGrid.appendChild(card); }); } diff --git a/js/my_profil.js b/js/my_profil.js index 9d7ee5f..9d47a5b 100644 --- a/js/my_profil.js +++ b/js/my_profil.js @@ -10,6 +10,8 @@ document.addEventListener('DOMContentLoaded', () => { const profileHeadline = document.getElementById('profile-headline'); const profileSubline = document.getElementById('profile-subline'); const logoutButton = document.getElementById('logout-button'); + const profileTabButtons = Array.from(document.querySelectorAll('[data-profile-tab]')); + const profileTabPanels = Array.from(document.querySelectorAll('[data-profile-panel]')); const myEventsCount = document.getElementById('my-events-count'); const myRegistrationsCount = document.getElementById('my-registrations-count'); @@ -36,6 +38,7 @@ document.addEventListener('DOMContentLoaded', () => { renderLoggedInState(currentUser); bindFormHandlers(); + activateProfileTab('hosting'); allEvents = await loadAllEvents(); renderMyEvents(allEvents, currentUser); @@ -80,6 +83,11 @@ document.addEventListener('DOMContentLoaded', () => { localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(registrationMap)); } + // Schreibt die lokal erstellten Events in den Storage. + function setStoredEvents(events) { + localStorage.setItem(EVENTS_STORAGE_KEY, JSON.stringify(events)); + } + // Fuehrt JSON-Daten und lokal erstellte Events in einer Liste zusammen. async function loadAllEvents() { try { @@ -119,6 +127,18 @@ document.addEventListener('DOMContentLoaded', () => { function bindFormHandlers() { profileForm.addEventListener('submit', handleProfileSubmit); myRegistrationsList.addEventListener('click', handleRegistrationListClick); + myEventsList.addEventListener('click', handleHostedListClick); + + profileTabButtons.forEach(button => { + button.addEventListener('click', () => { + const tabName = button.getAttribute('data-profile-tab'); + if (!tabName) { + return; + } + + activateProfileTab(tabName); + }); + }); [vornameInput, nachnameInput, emailInput, passwortInput].forEach(input => { input.addEventListener('input', () => { @@ -133,6 +153,53 @@ document.addEventListener('DOMContentLoaded', () => { }); } + // Reagiert auf Aktionen in der Liste "Meine Events" per Event Delegation. + function handleHostedListClick(event) { + const target = event.target; + if (!(target instanceof HTMLElement)) { + return; + } + + const cancelButton = target.closest('[data-cancel-event-id]'); + if (cancelButton && currentUser?.email) { + const eventId = Number(cancelButton.getAttribute('data-cancel-event-id')); + if (Number.isFinite(eventId)) { + cancelHostedEvent(eventId, currentUser.email); + } + return; + } + + if (target.closest('a, button')) { + return; + } + + const card = target.closest('[data-event-id]'); + if (!card) { + return; + } + + const eventId = Number(card.getAttribute('data-event-id')); + if (!Number.isFinite(eventId)) { + return; + } + + window.location.href = `event_detail.html?id=${eventId}`; + } + + // Schaltet den sichtbaren Profilbereich per Tabname um. + function activateProfileTab(tabName) { + profileTabButtons.forEach(button => { + const isActive = button.getAttribute('data-profile-tab') === tabName; + button.classList.toggle('is-active', isActive); + button.setAttribute('aria-selected', isActive ? 'true' : 'false'); + }); + + profileTabPanels.forEach(panel => { + const isActive = panel.getAttribute('data-profile-panel') === tabName; + panel.classList.toggle('hidden', !isActive); + }); + } + // Reagiert auf Aktionen in der Liste "Meine Anmeldungen" per Event Delegation. function handleRegistrationListClick(event) { const target = event.target; @@ -141,18 +208,68 @@ document.addEventListener('DOMContentLoaded', () => { } const unregisterButton = target.closest('[data-unregister-id]'); - if (!unregisterButton || !currentUser?.email) { + if (unregisterButton) { + if (!currentUser?.email) { + return; + } + + const eventId = Number(unregisterButton.getAttribute('data-unregister-id')); + if (!Number.isFinite(eventId)) { + return; + } + + unregisterFromEvent(eventId, currentUser.email); return; } - const eventId = Number(unregisterButton.getAttribute('data-unregister-id')); + if (target.closest('a, button')) { + return; + } + + const card = target.closest('[data-event-id]'); + if (!card) { + return; + } + + const eventId = Number(card.getAttribute('data-event-id')); if (!Number.isFinite(eventId)) { return; } - unregisterFromEvent(eventId, currentUser.email); + window.location.href = `event_detail.html?id=${eventId}`; } + + // Sagt ein gehostetes Event ab (aus eigener Profilansicht entfernen). + function cancelHostedEvent(eventId, userEmail) { + // Lokal erstellte, eigene Events werden direkt aus dem Storage geloescht. + const storedEvents = getStoredEvents(); + const nextStoredEvents = storedEvents.filter(event => { + const isTargetEvent = Number(event.id) === eventId; + const isOwnedByUser = normalizeText(event.hostEmail || '') === normalizeText(userEmail) + || normalizeText(event.host?.name || '') === normalizeText(currentUser?.vorname || ''); + + return !(isTargetEvent && isOwnedByUser); + }); + setStoredEvents(nextStoredEvents); + + // Event-ID fuer alle Benutzer aus den Anmeldungen entfernen. + const registrationMap = getRegistrationMap(); + Object.keys(registrationMap).forEach(email => { + const ids = Array.isArray(registrationMap[email]) + ? registrationMap[email].map(id => Number(id)).filter(Number.isFinite) + : []; + + registrationMap[email] = ids.filter(id => id !== eventId); + }); + setRegistrationMap(registrationMap); + + allEvents = allEvents.filter(event => Number(event.id) !== eventId); + + renderMyEvents(allEvents, currentUser); + renderMyRegistrations(allEvents, currentUser); + profileFeedback.textContent = 'Event wurde abgesagt und aus deinem Hosting entfernt.'; + } // Entfernt eine Event-ID aus der Benutzerliste und aktualisiert die UI sofort. function unregisterFromEvent(eventId, userEmail) { const registrationMap = getRegistrationMap(); @@ -260,15 +377,20 @@ document.addEventListener('DOMContentLoaded', () => { localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(map)); } - // Ermittelt gehostete Events anhand Host-E-Mail oder Host-Vorname. + // Ermittelt gehostete Events aus lokal erstellten Daten des aktuellen Benutzers. function getMyHostedEvents(events, user) { - const userFirstName = normalizeText(user.vorname); + const userFirstName = normalizeText(user.vorname || ''); + const userEmail = normalizeText(user.email || ''); return events.filter(event => { + if (event.source !== 'local') { + return false; + } + const hostEmail = normalizeText(event.hostEmail || ''); const hostName = normalizeText(event.host?.name || ''); - if (hostEmail && hostEmail === normalizeText(user.email)) { + if (hostEmail && hostEmail === userEmail) { return true; } @@ -289,45 +411,73 @@ document.addEventListener('DOMContentLoaded', () => { function renderMyEvents(events, user) { const hostedEvents = getMyHostedEvents(events, user); myEventsCount.textContent = String(hostedEvents.length); - renderEventCards(myEventsList, hostedEvents, 'Du hast noch kein eigenes Event erstellt.', false); + renderEventCards(myEventsList, hostedEvents, { + title: 'Noch kein eigenes Event', + text: 'Starte dein erstes Dinner und lade die Community an deinen Tisch ein.', + buttonLabel: 'Event erstellen', + href: 'event_create.html' + }, 'hosting'); } // Rendert angemeldete Events inkl. Zaehler. function renderMyRegistrations(events, user) { const registeredEvents = getMyRegisteredEvents(events, user); myRegistrationsCount.textContent = String(registeredEvents.length); - renderEventCards(myRegistrationsList, registeredEvents, 'Du bist aktuell bei keinem Event angemeldet.', true); + renderEventCards(myRegistrationsList, registeredEvents, { + title: 'Noch keine Anmeldungen', + text: 'Entdecke spannende Dinner in deiner Naehe und melde dich direkt an.', + buttonLabel: 'Events entdecken', + href: 'event_overview.html' + }, 'registrations'); } // Baut die Eventkarten fuer beide Listen in einheitlichem Markup. - function renderEventCards(container, events, emptyText, withUnregisterButton) { + function renderEventCards(container, events, emptyStateConfig, mode) { container.innerHTML = ''; if (events.length === 0) { - const emptyElement = document.createElement('p'); - emptyElement.className = 'profile-empty'; - emptyElement.textContent = emptyText; + const emptyElement = document.createElement('div'); + emptyElement.className = 'profile-empty-state'; + emptyElement.innerHTML = ` +

Keine Treffer

+

${emptyStateConfig.title}

+

${emptyStateConfig.text}

+ ${emptyStateConfig.buttonLabel} + `; container.appendChild(emptyElement); return; } events.forEach(event => { const card = document.createElement('article'); - card.className = 'profile-event-card'; + card.className = 'profile-event-card profile-event-card-clickable'; + card.setAttribute('data-event-id', String(event.id)); + const addressMarkup = mode === 'registrations' && event.address + ? ` +
+

Adresse

+

${event.address}

+
+ ` + : ''; - const actionMarkup = withUnregisterButton + const actionMarkup = mode === 'registrations' ? `
- Zum Event
` - : `Zum Event`; + : ` +
+ +
+ `; card.innerHTML = `

${event.title}

${event.location} | ${formatEventDate(event.date)} | ${formatEventTime(event.time)}

+ ${addressMarkup}
${actionMarkup} `; diff --git a/my_profil.html b/my_profil.html index f5610a9..da5bb7f 100644 --- a/my_profil.html +++ b/my_profil.html @@ -44,7 +44,13 @@
-
+ + +

Meine Events

0 @@ -52,7 +58,7 @@
-
+ -
+