From 1efa4dcd39878df3708bd5cd64f39e31bbf8bb73 Mon Sep 17 00:00:00 2001 From: viiivo <«vivien.vonburg@outlook.com»> Date: Fri, 10 Apr 2026 16:28:44 +0200 Subject: [PATCH] feat: Implement user profile management and navigation updates - Added a new profile page (my_profil.html) for users to manage their events and personal information. - Introduced a new CSS file (my_profil.css) for styling the profile page. - Created a JavaScript file (my_profil.js) to handle profile data retrieval, event registration management, and form submission. - Updated navigation logic (navigation.js) to dynamically display login/signup or event management links based on user authentication status. - Enhanced event creation and detail pages to support user-specific actions (registration/unregistration). - Improved login and signup processes to handle user data more robustly, including fallback user creation. - Refactored event overview to show user-specific events and registrations. - Added error handling and validation for user input in profile management. --- css/event_overview.css | 17 ++ css/my_profil.css | 251 ++++++++++++++++++++++++++ css/stylesheet_global.css | 19 ++ event_create.html | 5 +- event_detail.html | 5 +- event_overview.html | 5 +- index.html | 6 +- js/event_create.js | 18 +- js/event_detail.js | 91 +++++++++- js/event_overview.js | 39 +++- js/login.js | 52 +++++- js/my_profil.js | 367 ++++++++++++++++++++++++++++++++++++++ js/navigation.js | 63 +++++++ js/signup.js | 53 +++++- login.html | 6 +- my_profil.html | 102 +++++++++++ signup.html | 6 +- 17 files changed, 1076 insertions(+), 29 deletions(-) create mode 100644 css/my_profil.css create mode 100644 js/my_profil.js create mode 100644 js/navigation.js create mode 100644 my_profil.html diff --git a/css/event_overview.css b/css/event_overview.css index 30901c9..0c3e203 100644 --- a/css/event_overview.css +++ b/css/event_overview.css @@ -281,6 +281,14 @@ filter: brightness(0.95); } +.btn-primary-own, +.btn-primary-own:disabled { + background: var(--olive-light); + color: var(--black); + opacity: 1; + cursor: not-allowed; +} + /* --------------------------------------------------------- Overview Empty State --------------------------------------------------------- */ @@ -693,6 +701,15 @@ cursor: not-allowed; } +.detail-primary-btn-own, +.detail-primary-btn-own:disabled { + border-color: var(--olive-light); + background: var(--olive-light); + color: var(--black); + opacity: 1; + cursor: not-allowed; +} + /* --------------------------------------------------------- Responsive: Tablet (<= 850px) diff --git a/css/my_profil.css b/css/my_profil.css new file mode 100644 index 0000000..825504a --- /dev/null +++ b/css/my_profil.css @@ -0,0 +1,251 @@ +.profile-page { + /* Reserve a large safe zone below sticky nav so title/actions are never covered. */ + margin-top: 0; + padding-top: 6.5rem; + margin-bottom: var(--space-8); +} + +/* Kopfbereich mit Titel und Logout-Aktion. */ +.profile-hero { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: var(--space-4); + margin-bottom: var(--space-5); +} + +.profile-kicker { + margin: 0; + color: var(--olive); + font-size: 1rem; + font-weight: 500; + letter-spacing: var(--ls-label); +} + +#profile-headline { + margin: 0.4rem 0; + color: var(--brown); + font-size: clamp(2rem, 4.4vw, 2.8rem); +} + +.profile-subline { + margin: 0; + max-width: 48rem; +} + +.profile-logout { + border: none; + cursor: pointer; +} + +.profile-grid { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-4); +} + +/* Konsistentes Karten-Layout fuer alle Profilsektionen. */ +.profile-panel { + background: rgba(255, 255, 255, 0.88); + border-radius: var(--radius-lg); + box-shadow: 0 3px 12px rgba(102, 52, 13, 0.1); + padding: var(--space-5); +} + +.panel-head { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--space-3); +} + +.panel-title { + margin: 0; + color: var(--brown); + font-size: 1.8rem; +} + +.panel-count { + min-width: 2rem; + padding: 0.1rem 0.65rem; + border-radius: var(--radius-pill); + background: var(--olive-light); + color: var(--black); + font-size: 0.95rem; + font-weight: 600; + text-align: center; +} + +.profile-card-list { + display: grid; + gap: var(--space-2); +} + +/* Einzelne Eventkarte fuer "Meine Events" und "Meine Anmeldungen". */ +.profile-event-card { + border: 1px solid rgba(107, 107, 5, 0.25); + border-radius: var(--radius-md); + padding: var(--space-3); + display: flex; + justify-content: space-between; + align-items: center; + gap: var(--space-3); +} + +.profile-event-title { + margin: 0; + color: var(--black); + font-family: "Jost", sans-serif; + font-size: 1.25rem; + font-weight: 600; +} + +.profile-event-meta { + margin: 0.3rem 0 0; + font-size: 0.95rem; + color: var(--olive); +} + +.profile-event-link { + flex-shrink: 0; + color: var(--blue); + font-weight: 500; + text-decoration: none; +} + +.profile-event-link:hover, +.profile-event-link:focus-visible { + text-decoration: underline; + text-underline-offset: 3px; +} + +.profile-event-actions { + display: flex; + align-items: center; + gap: var(--space-2); +} + +.profile-unregister-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; +} + +.profile-unregister-btn:hover, +.profile-unregister-btn:focus-visible { + background: var(--tomato-dark); +} + +.profile-empty { + margin: 0; + color: var(--black); +} + +.form-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-3); +} + +.form-group { + margin-bottom: var(--space-3); +} + +.form-group label { + display: block; + margin-bottom: 0.35rem; + font-size: 0.95rem; + font-weight: 500; +} + +.form-group input { + width: 100%; + border: 1px solid #d8d8d8; + border-radius: var(--radius-sm); + background: var(--white); + padding: 0.7rem 0.85rem; + font-size: 1rem; +} + +.form-group input:focus { + outline: 2px solid rgba(107, 107, 5, 0.35); + outline-offset: 1px; +} + +.input-hint { + margin: 0.4rem 0 0; + font-size: 0.9rem; + color: #535353; +} + +.input-error { + margin-top: 0.35rem; + color: var(--error); + font-size: 0.85rem; + display: none; +} + +.form-group.has-error .input-error { + display: block; +} + +.form-group.has-error input { + border-color: var(--error); +} + +.profile-feedback { + margin: 0.75rem 0 0; + font-size: 0.95rem; + color: var(--olive); + min-height: 1.3rem; +} + +.profile-cta-row { + display: flex; + gap: var(--space-2); + margin-top: var(--space-3); +} + +.profile-button-secondary { + background: var(--tomato); +} + +.profile-button-secondary:hover { + background: var(--tomato-dark); +} + +@media (max-width: 48rem) { + .profile-page { + padding-top: 5.5rem; + } + + .profile-hero { + flex-direction: column; + align-items: stretch; + } + + .profile-logout { + width: max-content; + } + + .form-grid { + grid-template-columns: 1fr; + } + + .profile-event-card { + flex-direction: column; + align-items: flex-start; + } + + .profile-event-actions { + width: 100%; + justify-content: flex-start; + flex-wrap: wrap; + } +} diff --git a/css/stylesheet_global.css b/css/stylesheet_global.css index 7c358a8..2957091 100644 --- a/css/stylesheet_global.css +++ b/css/stylesheet_global.css @@ -210,6 +210,25 @@ p { color: var(--butter-light); } +/* Auth-Links in ausgeloggter Navigation: klarer Aktiv-/Default-Zustand. */ +.auth-nav-button--default { + background: transparent; + color: var(--olive); + border: 2px solid var(--olive); +} + +.auth-nav-button--default:hover, +.auth-nav-button--default:focus-visible { + background: var(--olive-light); + color: var(--black); +} + +.auth-nav-button--active { + background: var(--olive); + color: var(--butter-light); + border: 2px solid var(--olive); +} + .profile-pill { width: 2.375rem; height: 2.375rem; diff --git a/event_create.html b/event_create.html index 5f669db..8277154 100644 --- a/event_create.html +++ b/event_create.html @@ -9,6 +9,7 @@ + @@ -19,9 +20,7 @@ Invite Logo diff --git a/event_detail.html b/event_detail.html index 8a420a9..7820053 100644 --- a/event_detail.html +++ b/event_detail.html @@ -9,6 +9,7 @@ + @@ -19,9 +20,7 @@ Invite Logo diff --git a/event_overview.html b/event_overview.html index a289a24..c64e2e2 100644 --- a/event_overview.html +++ b/event_overview.html @@ -9,6 +9,7 @@ + @@ -19,9 +20,7 @@ Invite Logo diff --git a/index.html b/index.html index 8a9f43a..7adaa2d 100644 --- a/index.html +++ b/index.html @@ -9,6 +9,7 @@ + @@ -22,9 +23,8 @@ Invite Logo diff --git a/js/event_create.js b/js/event_create.js index 1428c64..e72d82d 100644 --- a/js/event_create.js +++ b/js/event_create.js @@ -15,6 +15,7 @@ const usernameElement = document.getElementById("username"); const flowFooter = document.getElementById("flowFooter"); const submissionSuccess = document.getElementById("submissionSuccess"); const EVENTS_STORAGE_KEY = "socialCookingEvents"; +const CURRENT_USER_KEY = "socialCookingCurrentUser"; // ============================= // STATE: aktueller Schritt im Flow @@ -36,8 +37,20 @@ const nextLabels = { 7: "Event veröffentlichen" }; -// Demo-Wert: Später könnte der Name z. B. aus einem User-Profil kommen -usernameElement.textContent = "Mia"; +// Liest den aktiven Benutzer aus localStorage und setzt den Anzeigenamen im Header. +function getCurrentUser() { + try { + const raw = localStorage.getItem(CURRENT_USER_KEY); + return raw ? JSON.parse(raw) : null; + } catch (error) { + console.error("Aktueller Benutzer konnte nicht gelesen werden:", error); + return null; + } +} + +const currentUser = getCurrentUser(); +const displayName = currentUser?.vorname?.trim() || "Mia"; +usernameElement.textContent = displayName; // ============================= @@ -409,6 +422,7 @@ function buildStoredEvent() { name: usernameElement.textContent.trim() || "Host", initial: (usernameElement.textContent.trim().charAt(0) || "H").toUpperCase() }, + hostEmail: currentUser?.email || "", hostMessage: [eventDescription], menu: buildMenuItems(menuDescription), specifications: getCheckboxValues("allergies") === "Keine Angabe" diff --git a/js/event_detail.js b/js/event_detail.js index 5b4bd6c..53bb7e2 100644 --- a/js/event_detail.js +++ b/js/event_detail.js @@ -1,10 +1,13 @@ document.addEventListener('DOMContentLoaded', async () => { const EVENTS_STORAGE_KEY = 'socialCookingEvents'; + const CURRENT_USER_KEY = 'socialCookingCurrentUser'; + const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations'; // ------------------------------------------------------------- // DOM entry point and shared asset path. // ------------------------------------------------------------- const detailContainer = document.getElementById('detail-view'); const locationIconPath = 'assets/location-pin.svg'; + const currentUser = getCurrentUser(); // Read event id from query string (detail page deep-link support). const params = new URLSearchParams(window.location.search); @@ -25,6 +28,49 @@ document.addEventListener('DOMContentLoaded', async () => { } } + function getCurrentUser() { + try { + const stored = localStorage.getItem(CURRENT_USER_KEY); + return stored ? JSON.parse(stored) : null; + } catch (error) { + console.error('Aktueller Benutzer konnte nicht gelesen werden.', error); + return null; + } + } + + 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)); + } + + // Ermittelt, ob das Event vom aktuell eingeloggten Benutzer erstellt wurde. + function isEventOwnedByCurrentUser(event, user) { + 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 fuer aeltere Datensaetze ohne hostEmail. + const userFirstName = String(user.vorname || '').trim().toLowerCase(); + const hostName = String(event.host?.name || '').trim().toLowerCase(); + return Boolean(userFirstName && hostName && userFirstName === hostName); + } + // Fetch data source and resolve the matching event record. try { const response = await fetch('data/events.json'); @@ -116,6 +162,14 @@ document.addEventListener('DOMContentLoaded', async () => { const confirmedGuests = participants.length; const freePlaces = Math.max(0, totalGuests - confirmedGuests); const isFull = freePlaces === 0; + 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 detailChips = [ `${eventCategory}`, `${dietLabel}`, @@ -200,8 +254,8 @@ document.addEventListener('DOMContentLoaded', async () => { ${isFull ? 'AUSGEBUCHT' : `${freePlaces} Plaetze frei`} - @@ -224,6 +278,39 @@ document.addEventListener('DOMContentLoaded', async () => { 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]'); + + // Anmeldung toggeln und im lokalen Registrierungs-Store persistieren. + if (registerButton) { + registerButton.addEventListener('click', () => { + 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); + + if (registrationSet.has(Number(event.id))) { + registrationSet.delete(Number(event.id)); + } else if (!isFull) { + registrationSet.add(Number(event.id)); + } + + nextRegistrationMap[currentUser.email] = Array.from(registrationSet); + setRegistrationMap(nextRegistrationMap); + + // Re-Render aktualisiert Buttonzustand und CTA ohne Seitenreload. + renderDetailPage(event); + }); + } // Central close helper to keep all close paths consistent. function closeLightbox() { diff --git a/js/event_overview.js b/js/event_overview.js index e92701c..15bf312 100644 --- a/js/event_overview.js +++ b/js/event_overview.js @@ -1,5 +1,6 @@ document.addEventListener('DOMContentLoaded', () => { const EVENTS_STORAGE_KEY = 'socialCookingEvents'; + const CURRENT_USER_KEY = 'socialCookingCurrentUser'; // ------------------------------------------------------------- // DOM references used throughout the page lifecycle. // ------------------------------------------------------------- @@ -14,6 +15,35 @@ document.addEventListener('DOMContentLoaded', () => { // ------------------------------------------------------------- let allEvents = []; let activeCategory = 'ALLE'; + const currentUser = getCurrentUser(); + + function getCurrentUser() { + try { + const stored = localStorage.getItem(CURRENT_USER_KEY); + return stored ? JSON.parse(stored) : null; + } catch (error) { + console.error('Aktueller Benutzer konnte nicht gelesen werden.', error); + return null; + } + } + + // Prueft, ob ein Event dem aktuellen Benutzer gehoert. + function isEventOwnedByCurrentUser(event, user) { + 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; + } + + const userFirstName = String(user.vorname || '').trim().toLowerCase(); + const hostName = String(event.host?.name || '').trim().toLowerCase(); + return Boolean(userFirstName && hostName && userFirstName === hostName); + } function getStoredEvents() { try { @@ -225,12 +255,19 @@ document.addEventListener('DOMContentLoaded', () => { const totalCapacity = event.spots; const freePlaces = Math.max(0, totalCapacity - bookedSeats); const isFull = freePlaces === 0; + const isOwnEvent = isEventOwnedByCurrentUser(event, currentUser); // Build optional specification chips only when data exists. const specsChips = event.specifications && event.specifications.length > 0 ? event.specifications.map(spec => `${spec}`).join('') : ''; + const actionMarkup = isOwnEvent + ? '' + : isFull + ? '' + : ''; + card.innerHTML = `
@@ -249,7 +286,7 @@ document.addEventListener('DOMContentLoaded', () => {
${isFull ? 'AUSGEBUCHT' : `${freePlaces} Plätze FREI`} - ${isFull ? '' : ''} + ${actionMarkup}
`; diff --git a/js/login.js b/js/login.js index e1c059c..5ba97fe 100644 --- a/js/login.js +++ b/js/login.js @@ -4,6 +4,42 @@ const passwortInput = document.getElementById('passwort'); const emailError = document.getElementById('emailError'); const passwortError = document.getElementById('passwortError'); +const USERS_STORAGE_KEY = 'socialCookingUsers'; +const CURRENT_USER_KEY = 'socialCookingCurrentUser'; + +// Liest alle registrierten Benutzer robust aus localStorage. +function getStoredUsers() { + try { + const raw = localStorage.getItem(USERS_STORAGE_KEY); + return raw ? JSON.parse(raw) : []; + } catch (error) { + console.error('Benutzerdaten konnten nicht gelesen werden.', error); + return []; + } +} + +// Speichert den aktiven Benutzer fuer nachfolgende Seiten. +function setCurrentUser(user) { + localStorage.setItem(CURRENT_USER_KEY, JSON.stringify(user)); +} + +// Erstellt einen Demo-Benutzer, falls fuer die E-Mail noch kein Account existiert. +function createFallbackUser(email, passwort) { + const localPart = email.split('@')[0] || 'Gast'; + const normalized = localPart.replace(/[._-]/g, ' ').trim(); + const guessedVorname = normalized ? normalized.split(' ')[0] : 'Gast'; + + return { + id: Date.now(), + vorname: guessedVorname.charAt(0).toUpperCase() + guessedVorname.slice(1), + nachname: '', + email, + passwort, + createdAt: new Date().toISOString(), + source: 'login-fallback' + }; +} + // Validierungsfunktion function validateForm(event) { event.preventDefault(); @@ -43,11 +79,21 @@ function validateForm(event) { passwortGroup.classList.remove('has-error'); } - // Wenn alle Validierungen bestanden, Form absenden + // Wenn alle Validierungen bestanden, Benutzer pruefen und Session speichern. if (isValid) { - //alert('Login erfolgreich! (Dies ist eine Demo)'); + const users = getStoredUsers(); + const matchedUser = users.find(user => user.email?.toLowerCase() === emailValue.toLowerCase()); - // Weiterleitung zur event overview Page + if (matchedUser && matchedUser.passwort !== passwortValue) { + passwortGroup.classList.add('has-error'); + passwortError.textContent = 'Das Passwort ist nicht korrekt.'; + return; + } + + const userToLogin = matchedUser || createFallbackUser(emailValue, passwortValue); + setCurrentUser(userToLogin); + + // Weiterleitung zur Event-Overview-Seite. window.location.href = 'event_overview.html'; } } diff --git a/js/my_profil.js b/js/my_profil.js new file mode 100644 index 0000000..9d7ee5f --- /dev/null +++ b/js/my_profil.js @@ -0,0 +1,367 @@ +document.addEventListener('DOMContentLoaded', () => { + const EVENTS_STORAGE_KEY = 'socialCookingEvents'; + const USERS_STORAGE_KEY = 'socialCookingUsers'; + const CURRENT_USER_KEY = 'socialCookingCurrentUser'; + const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations'; + + // Zentrale DOM-Referenzen fuer klare, testbare Funktionen. + const loggedOutState = document.getElementById('logged-out-state'); + const loggedInContent = document.getElementById('logged-in-content'); + const profileHeadline = document.getElementById('profile-headline'); + const profileSubline = document.getElementById('profile-subline'); + const logoutButton = document.getElementById('logout-button'); + + const myEventsCount = document.getElementById('my-events-count'); + const myRegistrationsCount = document.getElementById('my-registrations-count'); + const myEventsList = document.getElementById('my-events-list'); + const myRegistrationsList = document.getElementById('my-registrations-list'); + + const profileForm = document.getElementById('profile-form'); + const profileFeedback = document.getElementById('profile-feedback'); + const vornameInput = document.getElementById('vorname'); + const nachnameInput = document.getElementById('nachname'); + const emailInput = document.getElementById('email'); + const passwortInput = document.getElementById('passwort'); + + let currentUser = getCurrentUser(); + let allEvents = []; + + init(); + + async function init() { + if (!currentUser) { + renderLoggedOutState(); + return; + } + + renderLoggedInState(currentUser); + bindFormHandlers(); + + allEvents = await loadAllEvents(); + renderMyEvents(allEvents, currentUser); + renderMyRegistrations(allEvents, currentUser); + } + + // Liest den aktuell eingeloggten Benutzer robust aus dem Storage. + function getCurrentUser() { + try { + const raw = localStorage.getItem(CURRENT_USER_KEY); + return raw ? JSON.parse(raw) : null; + } catch (error) { + console.error('Der aktuelle Benutzer konnte nicht geladen werden.', error); + return null; + } + } + + // Liest lokal erstellte Events aus dem Storage. + function getStoredEvents() { + try { + const raw = localStorage.getItem(EVENTS_STORAGE_KEY); + return raw ? JSON.parse(raw) : []; + } catch (error) { + console.error('Lokale Events konnten nicht gelesen werden.', error); + return []; + } + } + + // Liest den Anmeldestatus pro Benutzer-E-Mail. + function getRegistrationMap() { + try { + const raw = localStorage.getItem(REGISTRATION_STORAGE_KEY); + return raw ? JSON.parse(raw) : {}; + } catch (error) { + console.error('Anmeldedaten konnten nicht gelesen werden.', error); + return {}; + } + } + + // Schreibt den gesamten Registrierungszustand in localStorage. + function setRegistrationMap(registrationMap) { + localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(registrationMap)); + } + + // Fuehrt JSON-Daten und lokal erstellte Events in einer Liste zusammen. + async function loadAllEvents() { + try { + const response = await fetch('data/events.json'); + const apiEvents = await response.json(); + return [...getStoredEvents(), ...apiEvents]; + } catch (error) { + console.error('Events konnten nicht geladen werden.', error); + return getStoredEvents(); + } + } + + // Schaltet in den ausgeloggten Zustand und blendet geschuetzte Inhalte aus. + function renderLoggedOutState() { + loggedOutState.classList.remove('hidden'); + loggedInContent.classList.add('hidden'); + logoutButton.classList.add('hidden'); + profileHeadline.textContent = 'Mein Profil'; + profileSubline.textContent = 'Bitte logge dich ein, um deinen Bereich zu sehen.'; + } + + // Fuellt Ueberschriften und Formular mit den aktuellen Benutzerdaten. + function renderLoggedInState(user) { + loggedOutState.classList.add('hidden'); + loggedInContent.classList.remove('hidden'); + logoutButton.classList.remove('hidden'); + + profileHeadline.textContent = `Hallo ${user.vorname || 'Gast'}`; + profileSubline.textContent = 'Hier kannst du deine Events und Anmeldungen verwalten.'; + + vornameInput.value = user.vorname || ''; + nachnameInput.value = user.nachname || ''; + emailInput.value = user.email || ''; + } + + // Bindet Submit-, Input- und Logout-Verhalten an die Profilseite. + function bindFormHandlers() { + profileForm.addEventListener('submit', handleProfileSubmit); + myRegistrationsList.addEventListener('click', handleRegistrationListClick); + + [vornameInput, nachnameInput, emailInput, passwortInput].forEach(input => { + input.addEventListener('input', () => { + input.parentElement.classList.remove('has-error'); + profileFeedback.textContent = ''; + }); + }); + + logoutButton.addEventListener('click', () => { + localStorage.removeItem(CURRENT_USER_KEY); + window.location.href = 'login.html'; + }); + } + + // Reagiert auf Aktionen in der Liste "Meine Anmeldungen" per Event Delegation. + function handleRegistrationListClick(event) { + const target = event.target; + if (!(target instanceof HTMLElement)) { + return; + } + + const unregisterButton = target.closest('[data-unregister-id]'); + if (!unregisterButton || !currentUser?.email) { + return; + } + + const eventId = Number(unregisterButton.getAttribute('data-unregister-id')); + if (!Number.isFinite(eventId)) { + return; + } + + unregisterFromEvent(eventId, currentUser.email); + } + + // Entfernt eine Event-ID aus der Benutzerliste und aktualisiert die UI sofort. + function unregisterFromEvent(eventId, userEmail) { + const registrationMap = getRegistrationMap(); + const currentIds = Array.isArray(registrationMap[userEmail]) ? registrationMap[userEmail] : []; + const nextIds = currentIds + .map(id => Number(id)) + .filter(id => Number.isFinite(id) && id !== eventId); + + registrationMap[userEmail] = nextIds; + setRegistrationMap(registrationMap); + + renderMyRegistrations(allEvents, currentUser); + profileFeedback.textContent = 'Du wurdest von dem Event abgemeldet.'; + } + + // Validiert Profildaten konsistent und liefert true/false zur Submit-Steuerung. + function validateProfileForm() { + let isValid = true; + + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + + if (!vornameInput.value.trim()) { + vornameInput.parentElement.classList.add('has-error'); + isValid = false; + } + + if (!nachnameInput.value.trim()) { + nachnameInput.parentElement.classList.add('has-error'); + isValid = false; + } + + if (!emailRegex.test(emailInput.value.trim())) { + emailInput.parentElement.classList.add('has-error'); + isValid = false; + } + + if (passwortInput.value && passwortInput.value.length < 6) { + passwortInput.parentElement.classList.add('has-error'); + isValid = false; + } + + return isValid; + } + + // Speichert Profilaenderungen lokal und synchronisiert auch den Benutzerkatalog. + function handleProfileSubmit(event) { + event.preventDefault(); + + if (!validateProfileForm()) { + profileFeedback.textContent = 'Bitte pruefe die markierten Felder.'; + return; + } + + const previousEmail = currentUser.email; + const nextUser = { + ...currentUser, + vorname: vornameInput.value.trim(), + nachname: nachnameInput.value.trim(), + email: emailInput.value.trim(), + passwort: passwortInput.value ? passwortInput.value : currentUser.passwort, + updatedAt: new Date().toISOString() + }; + + currentUser = nextUser; + localStorage.setItem(CURRENT_USER_KEY, JSON.stringify(nextUser)); + syncUserInUserStore(previousEmail, nextUser); + + // Falls sich die E-Mail geaendert hat, verschieben wir bestehende Anmeldungen auf die neue E-Mail. + migrateRegistrationEmail(previousEmail, nextUser.email); + + passwortInput.value = ''; + profileHeadline.textContent = `Hallo ${nextUser.vorname}`; + profileFeedback.textContent = 'Profil erfolgreich gespeichert.'; + } + + // Synchronisiert einen Benutzer im zentralen User-Array. + function syncUserInUserStore(previousEmail, nextUser) { + let users = []; + + try { + const raw = localStorage.getItem(USERS_STORAGE_KEY); + users = raw ? JSON.parse(raw) : []; + } catch (error) { + console.error('Benutzerdaten konnten nicht gelesen werden.', error); + } + + const nextUsers = users.filter(user => user.email !== previousEmail && user.email !== nextUser.email); + nextUsers.unshift(nextUser); + localStorage.setItem(USERS_STORAGE_KEY, JSON.stringify(nextUsers)); + } + + // Migriert bestehende Registrierungen, falls die E-Mail aktualisiert wurde. + function migrateRegistrationEmail(previousEmail, nextEmail) { + if (!previousEmail || !nextEmail || previousEmail === nextEmail) { + return; + } + + const map = getRegistrationMap(); + const existingRegistrations = Array.isArray(map[previousEmail]) ? map[previousEmail] : []; + const alreadyPresent = Array.isArray(map[nextEmail]) ? map[nextEmail] : []; + + map[nextEmail] = Array.from(new Set([...alreadyPresent, ...existingRegistrations])); + delete map[previousEmail]; + + localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(map)); + } + + // Ermittelt gehostete Events anhand Host-E-Mail oder Host-Vorname. + function getMyHostedEvents(events, user) { + const userFirstName = normalizeText(user.vorname); + + return events.filter(event => { + const hostEmail = normalizeText(event.hostEmail || ''); + const hostName = normalizeText(event.host?.name || ''); + + if (hostEmail && hostEmail === normalizeText(user.email)) { + return true; + } + + return userFirstName && hostName === userFirstName; + }); + } + + // Ermittelt angemeldete Events ueber die Registration-Map. + function getMyRegisteredEvents(events, user) { + const registrationMap = getRegistrationMap(); + const registeredIds = Array.isArray(registrationMap[user.email]) ? registrationMap[user.email] : []; + const idSet = new Set(registeredIds.map(id => Number(id))); + + return events.filter(event => idSet.has(Number(event.id))); + } + + // Rendert gehostete Events inkl. Zaehler. + 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); + } + + // 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); + } + + // Baut die Eventkarten fuer beide Listen in einheitlichem Markup. + function renderEventCards(container, events, emptyText, withUnregisterButton) { + container.innerHTML = ''; + + if (events.length === 0) { + const emptyElement = document.createElement('p'); + emptyElement.className = 'profile-empty'; + emptyElement.textContent = emptyText; + container.appendChild(emptyElement); + return; + } + + events.forEach(event => { + const card = document.createElement('article'); + card.className = 'profile-event-card'; + + const actionMarkup = withUnregisterButton + ? ` +
+ Zum Event + +
+ ` + : `Zum Event`; + + card.innerHTML = ` +
+

${event.title}

+

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

+
+ ${actionMarkup} + `; + + container.appendChild(card); + }); + } + + // Formatiert ein Eventdatum konsistent fuer die Profilkarten. + function formatEventDate(dateString) { + if (!dateString) { + return 'Kein Datum'; + } + + if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) { + const [year, month, day] = dateString.split('-'); + const monthLabel = ['Januar', 'Februar', 'Maerz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'][Number(month) - 1]; + return `${Number(day)}. ${monthLabel} ${year}`; + } + + return dateString; + } + + // Vereinheitlicht die Zeitanzeige fuer die Profilseite. + function formatEventTime(timeString) { + if (!timeString) { + return 'Keine Uhrzeit'; + } + + return timeString.includes('UHR') ? timeString.replace('UHR', 'Uhr').trim() : timeString; + } + + // Normalisiert Vergleichswerte fuer robuste String-Matches. + function normalizeText(value) { + return String(value || '').trim().toLowerCase(); + } +}); diff --git a/js/navigation.js b/js/navigation.js new file mode 100644 index 0000000..ffe8264 --- /dev/null +++ b/js/navigation.js @@ -0,0 +1,63 @@ +document.addEventListener('DOMContentLoaded', () => { + const CURRENT_USER_KEY = 'socialCookingCurrentUser'; + const navContainers = document.querySelectorAll('.nav-tab-links'); + const currentPage = (window.location.pathname.split('/').pop() || 'index.html').toLowerCase(); + + // Beendet frueh, falls auf einer Seite keine Hauptnavigation vorhanden ist. + if (!navContainers.length) { + return; + } + + // Liest den aktiven Benutzer robust aus localStorage. + function getCurrentUser() { + try { + const stored = localStorage.getItem(CURRENT_USER_KEY); + return stored ? JSON.parse(stored) : null; + } catch (error) { + console.error('Aktueller Benutzer konnte nicht gelesen werden.', error); + return null; + } + } + + // Baut die Navigation fuer ausgeloggte Besucher. + function buildLoggedOutNavigation() { + const loginIsActive = currentPage === 'login.html'; + const signupIsActive = currentPage === 'signup.html'; + + return ` + + Login + + + Signup + + `; + } + + // Baut die Navigation fuer eingeloggte Benutzer. + function buildLoggedInNavigation() { + return ` + Event finden + Event erstellen + Mein Profil + `; + } + + const currentUser = getCurrentUser(); + const nextMarkup = currentUser ? buildLoggedInNavigation() : buildLoggedOutNavigation(); + + // Wendet das passende Markup auf alle vorhandenen Kopf-Navigationen an. + navContainers.forEach(container => { + container.innerHTML = nextMarkup; + }); +}); diff --git a/js/signup.js b/js/signup.js index f158cd4..87a1b64 100644 --- a/js/signup.js +++ b/js/signup.js @@ -5,6 +5,30 @@ const emailInput = document.getElementById('email'); const passwortInput = document.getElementById('passwort'); const welcomeModal = document.getElementById('welcomeModal'); +const USERS_STORAGE_KEY = 'socialCookingUsers'; +const CURRENT_USER_KEY = 'socialCookingCurrentUser'; + +// Liest bestehende Benutzerliste robust aus localStorage. +function getStoredUsers() { + try { + const raw = localStorage.getItem(USERS_STORAGE_KEY); + return raw ? JSON.parse(raw) : []; + } catch (error) { + console.error('Benutzerdaten konnten nicht gelesen werden.', error); + return []; + } +} + +// Schreibt die komplette Benutzerliste in localStorage. +function setStoredUsers(users) { + localStorage.setItem(USERS_STORAGE_KEY, JSON.stringify(users)); +} + +// Speichert den aktiven Benutzer fuer nachfolgende Seiten. +function setCurrentUser(user) { + localStorage.setItem(CURRENT_USER_KEY, JSON.stringify(user)); +} + // Funktion zum Öffnen des Welcome Modals function openWelcomeModal() { welcomeModal.classList.add('show'); @@ -78,12 +102,35 @@ function validateForm(event) { passwortGroup.classList.remove('has-error'); } - // Wenn alle Validierungen bestanden, Modal anzeigen + // Wenn alle Validierungen bestanden, Benutzer speichern und Session setzen. if (isValid) { + const existingUsers = getStoredUsers(); + const emailLower = emailValue.toLowerCase(); + const emailAlreadyUsed = existingUsers.some(user => user.email?.toLowerCase() === emailLower); + + if (emailAlreadyUsed) { + emailGroup.classList.add('has-error'); + document.getElementById('emailError').textContent = 'Diese E-Mail ist bereits registriert. Bitte nutze den Login.'; + return; + } + + const newUser = { + id: Date.now(), + vorname: vornameValue, + nachname: nachnameValue, + email: emailValue, + passwort: passwortValue, + createdAt: new Date().toISOString(), + source: 'signup' + }; + + setStoredUsers([newUser, ...existingUsers]); + setCurrentUser(newUser); + openWelcomeModal(); - // Hier würde später die Registrierung zum Backend gesendet + // Hier würde spaeter die Registrierung zum Backend gesendet. - // Weiterleitung zur event overview Page + // Weiterleitung zur Event-Overview-Seite. window.location.href = 'event_overview.html'; } } diff --git a/login.html b/login.html index 16d2ea7..67f3daa 100644 --- a/login.html +++ b/login.html @@ -9,6 +9,7 @@ + @@ -20,9 +21,8 @@ Invite Logo
diff --git a/my_profil.html b/my_profil.html new file mode 100644 index 0000000..f5610a9 --- /dev/null +++ b/my_profil.html @@ -0,0 +1,102 @@ + + + + + + Mein Profil | Invité + + + + + + + + + +
+
+ + Invite Logo + + +
+
+ +
+
+
+

Mein Bereich

+

Mein Profil

+

Hier findest du deine Events, deine Anmeldungen und kannst deine Profildaten verwalten.

+
+ +
+ + + +
+
+
+

Meine Events

+ 0 +
+
+
+ +
+
+

Meine Anmeldungen

+ 0 +
+
+
+ +
+

Profil verwalten

+
+
+
+ + +

Bitte gib deinen Vornamen ein.

+
+ +
+ + +

Bitte gib deinen Nachnamen ein.

+
+
+ +
+ + +

Bitte gib eine gültige E-Mail-Adresse ein.

+
+ +
+ + +

Nur ausfüllen, wenn du dein Passwort ändern möchtest.

+

Das Passwort muss mindestens 6 Zeichen lang sein.

+
+ + +

+
+
+
+
+ + + + diff --git a/signup.html b/signup.html index f54de99..22e258d 100644 --- a/signup.html +++ b/signup.html @@ -9,6 +9,7 @@ + @@ -19,9 +20,8 @@ Invite Logo