This commit is contained in:
@@ -61,17 +61,18 @@ Stand: 2026-07-22
|
||||
6. Aktueller Funktionsstand
|
||||
- Startseite mit Hero, Suche, neueste Treffen, Community-Vorschau, Mitgliederbereich.
|
||||
- Hauptnavigation aktuell reduziert auf `Home`, `Suche`, `Community`.
|
||||
- Für eingeloggte Nutzer gibt es rechts ein Profil-Dropdown mit `Übersicht`, `Mitgliederbereich`, `Abmelden`.
|
||||
- Für eingeloggte Nutzer gibt es rechts ein Profil-Dropdown mit Direktlinks zu `Profil`, `Kinder`, `Termine`, `Community`, `Einstellungen`, `Abmelden`.
|
||||
- Kein eigener Top-Level-Punkt `Termine`; Event-Fokus liegt derzeit auf Startseite und Suche.
|
||||
- Community mit Kategorien, Boards, Thread-Liste, Thread-Ansicht, Rollen-/Moderationslogik.
|
||||
- Dashboard mit Profil, Kinder, eigene Events, Community-Status.
|
||||
- Mitgliederbereich als Bereichslayout mit linker Navigation und separaten Seiten für Profil, Kinder, Termine, Community und Einstellungen.
|
||||
- Community-Admin-Bereich für Bewerbungen, Meldungen, Rollen und Migration.
|
||||
- Impressum, Datenschutz-&-Cookies-Seite und Über-uns vorhanden.
|
||||
- Standortsortierung auf der Startseite für Treffen in der Nähe.
|
||||
- Standortpräferenz im Profil:
|
||||
- Standortpräferenz im Einstellungsbereich:
|
||||
- `disabled`
|
||||
- `prompt`
|
||||
- `enabled`
|
||||
- Im Einstellungsbereich stehen zusätzlich Browser-Hinweise zur Standortfreigabe sowie der Einstieg in die Consent-Verwaltung.
|
||||
- Consent-Manager:
|
||||
- notwendige Cookies immer aktiv
|
||||
- `analytics` für Matomo
|
||||
|
||||
@@ -22,7 +22,8 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter. Kernbereiche sind l
|
||||
|
||||
## Navigation / Konto
|
||||
- Eingeloggte Nutzer sehen rechts ein Profil-Menü statt einzelner `Dashboard`-/`Logout`-Buttons.
|
||||
- Das Profil-Menü enthält aktuell `Übersicht`, `Mitgliederbereich`, `Abmelden`.
|
||||
- Das Profil-Menü enthält aktuell `Profil`, `Kinder`, `Termine`, `Community`, `Einstellungen`, `Abmelden`.
|
||||
- Der Mitgliederbereich selbst ist als Layout mit linker Bereichsnavigation aufgebaut.
|
||||
- Debug ist kein Menüpunkt und erscheint nur als Floating-Käfer für `site_admin` bei aktivem Debug-Modus.
|
||||
|
||||
## Community und Rechte
|
||||
@@ -40,10 +41,11 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter. Kernbereiche sind l
|
||||
- `external_services`
|
||||
- Matomo wird nur nach Analyse-Einwilligung geladen.
|
||||
- Externe Karten-/Standortdienste und lokale Standortspeicherung hängen an `external_services`.
|
||||
- Zusätzlich gibt es eine Standortpräferenz im Profil:
|
||||
- Zusätzlich gibt es eine Standortpräferenz im Bereich `Einstellungen`:
|
||||
- `disabled`
|
||||
- `prompt`
|
||||
- `enabled`
|
||||
- Im Einstellungsbereich werden außerdem Browser-Grenzen erklärt: Die Browser-Freigabe selbst kann nicht von der Website gesetzt oder aufgehoben werden.
|
||||
- Es gibt eine eigene Seite `/datenschutz` für Datenschutz- und Cookie-Hinweise sowie einen Footer-Link auf die Consent-Einstellungen.
|
||||
|
||||
## Doku-Regel
|
||||
|
||||
@@ -16,10 +16,11 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf lokale
|
||||
- Community-Admin-Bereich für Bewerbungen, Meldungen, Rollen und Migration
|
||||
- Impressum, Datenschutz-&-Cookies-Seite und Über-uns
|
||||
- standortbasierte Sortierung für die neuesten Treffen
|
||||
- Standortpräferenz im Profil
|
||||
- eigener Einstellungsbereich für Standortfreigabe, Browser-Hinweise und Consent-Verwaltung
|
||||
- Consent-Manager für Analyse und externe Dienste
|
||||
- Navigation aktuell mit `Home`, `Suche`, `Community`
|
||||
- eingeloggte Nutzer sehen rechts ein Profil-Menü statt separater `Dashboard`-/`Logout`-Buttons
|
||||
- eingeloggte Nutzer sehen rechts ein Profil-Menü mit Direktlinks zu `Profil`, `Kinder`, `Termine`, `Community`, `Einstellungen`
|
||||
- der Mitgliederbereich ist als Seitenlayout mit linker Bereichsnavigation aufgebaut
|
||||
- Debug-Floating-Button nur für `site_admin` im Debug-Modus
|
||||
|
||||
## Technik
|
||||
|
||||
@@ -6,6 +6,16 @@ $editing = isset($editEvent) && $editEvent !== null;
|
||||
$actionEvent = $editing ? 'event_update' : 'event_add';
|
||||
$startVal = $editEvent ? date('Y-m-d\TH:i', strtotime((string)$editEvent['starts_at'])) : '';
|
||||
$allowNoKidsChecked = $editEvent ? ((int)$editEvent['allow_kids'] === 0) : false;
|
||||
$sectionLinks = [
|
||||
'profile' => 'Profil',
|
||||
'children' => 'Kinder',
|
||||
'events' => 'Termine',
|
||||
'community' => 'Community',
|
||||
'settings' => 'Einstellungen',
|
||||
];
|
||||
if (in_array('admin', $allowedSections ?? [], true)) {
|
||||
$sectionLinks['admin'] = 'Admin';
|
||||
}
|
||||
?>
|
||||
<main class="section">
|
||||
<div class="container" style="display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:12px;">
|
||||
@@ -29,177 +39,272 @@ $allowNoKidsChecked = $editEvent ? ((int)$editEvent['allow_kids'] === 0) : false
|
||||
</div>
|
||||
|
||||
<div class="container dash-section">
|
||||
<div class="dash-grid">
|
||||
<?php if (in_array('forum_admin', $communityRoles ?? [], true) || !empty($communityIsSiteAdmin)): ?>
|
||||
<div class="card dash-card">
|
||||
<div class="badge">Admin</div>
|
||||
<h3>Community-Verwaltung</h3>
|
||||
<div class="flex gap-12" style="margin:12px 0; flex-wrap:wrap;">
|
||||
<a class="btn" href="/community-admin">Zum Admin-Bereich</a>
|
||||
<div class="account-layout">
|
||||
<aside class="account-sidebar">
|
||||
<div class="account-sidebar__inner">
|
||||
<div class="account-sidebar__head">
|
||||
<span class="badge">Mitgliederbereich</span>
|
||||
<h2>Navigation</h2>
|
||||
</div>
|
||||
<?php if ($communityMigrationStatus): ?>
|
||||
<ul class="dash-list">
|
||||
<li>Status: <?= !empty($communityMigrationStatus['complete']) ? 'Vollständig eingerichtet' : 'Migration noch offen' ?></li>
|
||||
<li>Fehlende Bausteine: <?= !empty($communityMigrationStatus['missing']) ? htmlspecialchars(implode(', ', $communityMigrationStatus['missing']), ENT_QUOTES) : 'Keine' ?></li>
|
||||
</ul>
|
||||
<?php else: ?>
|
||||
<p class="muted small">Migrationsstatus konnte nicht ermittelt werden.</p>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($communityIsSiteAdmin)): ?>
|
||||
<form method="post" style="margin-top:12px;">
|
||||
<input type="hidden" name="action" value="community_migrate">
|
||||
<button class="btn" type="submit">Community-Migration ausführen</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<p class="muted small" style="margin-top:10px;">Bündelt Moderation, Bewerbungen, Rollen und bei Bedarf auch die Community-Migration.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card dash-card">
|
||||
<div class="badge">Community</div>
|
||||
<h3>Dein Community-Status</h3>
|
||||
<ul class="dash-list">
|
||||
<li>Rang: <?= htmlspecialchars(trim(($communityLevel['icon'] ?? '') . ' ' . ($communityLevel['label'] ?? '')), ENT_QUOTES) ?></li>
|
||||
<li>Punkte: <?= number_format((float)$communityPoints, 1, ',', '.') ?></li>
|
||||
<li>Rollen: <?= htmlspecialchars($communityRoles ? implode(', ', $communityRoles) : 'Keine besonderen Rollen', ENT_QUOTES) ?></li>
|
||||
<li>Themen erstellen: <?= $communityRestrictions['thread_create_blocked'] ? 'Gesperrt' : 'Erlaubt' ?></li>
|
||||
<li>Antworten schreiben: <?= $communityRestrictions['reply_blocked'] ? 'Gesperrt' : 'Erlaubt' ?></li>
|
||||
</ul>
|
||||
<?php if (!empty($communityRestrictions['reason'])): ?>
|
||||
<p class="muted small" style="margin-top:10px;">Hinweis zur Community-Sperre: <?= htmlspecialchars((string)$communityRestrictions['reason'], ENT_QUOTES) ?></p>
|
||||
<?php endif; ?>
|
||||
<?php if ($communityCanApply): ?>
|
||||
<form method="post" class="stack gap-6" style="margin-top:12px;">
|
||||
<input type="hidden" name="action" value="community_admin_apply">
|
||||
<label class="label" for="communityMotivation">Bewerbung als Forum-Admin</label>
|
||||
<textarea id="communityMotivation" name="motivation" class="textarea" rows="4" placeholder="Warum möchtest du die Community als Forum-Admin unterstützen?" required></textarea>
|
||||
<button class="btn" type="submit">Bewerbung absenden</button>
|
||||
</form>
|
||||
<?php elseif ($communityApplication): ?>
|
||||
<div style="margin-top:12px;">
|
||||
<p class="muted small" style="margin:0;">Letzte Bewerbung: <strong><?= htmlspecialchars((string)$communityApplication['status'], ENT_QUOTES) ?></strong></p>
|
||||
<?php if (!empty($communityApplication['decision_reason'])): ?>
|
||||
<p class="muted small" style="margin-top:6px;"><?= htmlspecialchars((string)$communityApplication['decision_reason'], ENT_QUOTES) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="card dash-card">
|
||||
<div class="badge">Profil</div>
|
||||
<h3>Deine Angaben</h3>
|
||||
<ul class="dash-list">
|
||||
<li>Name: <?= htmlspecialchars(trim($profile['first_name'] . ' ' . $profile['last_name']), ENT_QUOTES) ?></li>
|
||||
<li>Anzeigename: <?= htmlspecialchars($profile['display_name'], ENT_QUOTES) ?></li>
|
||||
<li>Ort: <?= htmlspecialchars($profile['city'], ENT_QUOTES) ?> <?= htmlspecialchars($profile['zip'], ENT_QUOTES) ?></li>
|
||||
<li>E-Mail: <?= htmlspecialchars($profile['email'], ENT_QUOTES) ?></li>
|
||||
<li>Telefon: <?= htmlspecialchars($profile['contact_phone'], ENT_QUOTES) ?></li>
|
||||
<li>Standortfreigabe: <?=
|
||||
htmlspecialchars(match ($profile['location_tracking_preference'] ?? 'prompt') {
|
||||
'enabled' => 'Immer verwenden',
|
||||
'disabled' => 'Deaktiviert',
|
||||
default => 'Beim nächsten Mal fragen',
|
||||
}, ENT_QUOTES)
|
||||
?></li>
|
||||
<li>Beruf: <?= htmlspecialchars($profile['profession'], ENT_QUOTES) ?></li>
|
||||
<li>Sprachen: <?= htmlspecialchars($profile['languages'], ENT_QUOTES) ?></li>
|
||||
<li>About: <?= htmlspecialchars($profile['about'], ENT_QUOTES) ?></li>
|
||||
</ul>
|
||||
<div class="flex gap-12" style="margin-top:12px;">
|
||||
<button class="btn" type="button" data-modal-open="modalProfile">Bearbeiten</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card dash-card">
|
||||
<div class="badge">Kinder</div>
|
||||
<h3>Deine Kids</h3>
|
||||
<?php if (!$children): ?>
|
||||
<p class="muted small">Noch keine Kinder eingetragen.</p>
|
||||
<?php else: ?>
|
||||
<ul class="dash-list">
|
||||
<?php foreach ($children as $c): ?>
|
||||
<li><?= htmlspecialchars($c['first_name'], ENT_QUOTES) ?>, <?= htmlspecialchars($c['gender'], ENT_QUOTES) ?> <?= $c['age_years'] ? '(' . (int)$c['age_years'] . ' Jahre)' : '' ?></li>
|
||||
<nav class="account-sidebar__nav">
|
||||
<?php foreach ($sectionLinks as $sectionKey => $sectionLabel): ?>
|
||||
<a href="/dashboard?section=<?= rawurlencode($sectionKey) ?>" class="<?= $section === $sectionKey ? 'is-active' : '' ?>">
|
||||
<span><?= htmlspecialchars($sectionLabel, ENT_QUOTES) ?></span>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
<div class="flex gap-12" style="margin-top:12px;">
|
||||
<button class="btn" type="button" data-modal-open="modalChild">Kind hinzufügen</button>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="container dash-section" id="events">
|
||||
<div class="card dash-card">
|
||||
<div class="badge">Deine Events</div>
|
||||
<div class="flex gap-12" style="margin:10px 0 16px 0; flex-wrap: wrap;">
|
||||
<button class="btn" type="button" data-modal-open="modalEvent">Event anlegen</button>
|
||||
</div>
|
||||
<?php if (!$eventsUpcoming): ?>
|
||||
<p class="muted small">Keine zukünftigen Events angelegt.</p>
|
||||
<?php else: ?>
|
||||
<ul class="dash-list" style="margin-top:10px;">
|
||||
<?php foreach ($eventsUpcoming as $e): ?>
|
||||
<li>
|
||||
<div style="display:flex; justify-content:space-between; gap:12px; align-items:center; flex-wrap: wrap;">
|
||||
<div>
|
||||
<strong><?= htmlspecialchars($e['title'], ENT_QUOTES) ?></strong>
|
||||
<?php if ($e['status'] === 'cancelled'): ?>
|
||||
<span class="badge" style="background:#fee2e2; color:#991b1b;">Abgesagt</span>
|
||||
<?php endif; ?>
|
||||
<div class="muted small" style="margin-top:4px;">
|
||||
<?= htmlspecialchars($e['city'], ENT_QUOTES) ?> · <?= htmlspecialchars($e['starts_at'], ENT_QUOTES) ?> · <?= htmlspecialchars($e['visibility'], ENT_QUOTES) ?>
|
||||
· Anmeldungen: <?= (int)$e['participant_count'] ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-8" style="flex-wrap: wrap;">
|
||||
<a class="btn ghost" href="/dashboard?edit_event=<?= (int)$e['id'] ?>#events">Bearbeiten</a>
|
||||
<?php if ((int)$e['participant_count'] === 0): ?>
|
||||
<form method="post" action="/dashboard#events" onsubmit="return confirm('Event wirklich löschen?');">
|
||||
<input type="hidden" name="action" value="event_delete">
|
||||
<input type="hidden" name="event_id" value="<?= (int)$e['id'] ?>">
|
||||
<button class="btn ghost" type="submit">Löschen</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<?php if ($e['status'] !== 'cancelled'): ?>
|
||||
<form method="post" action="/dashboard#events" onsubmit="return confirm('Event für alle absagen?');">
|
||||
<input type="hidden" name="action" value="event_cancel">
|
||||
<input type="hidden" name="event_id" value="<?= (int)$e['id'] ?>">
|
||||
<button class="btn ghost" type="submit">Absagen</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="account-main">
|
||||
<?php if ($section === 'profile'): ?>
|
||||
<section class="account-panel">
|
||||
<div class="account-panel__head">
|
||||
<h2>Dein Profil</h2>
|
||||
<p class="muted">Hier pflegst du deine persönlichen Angaben für den Mitgliederbereich.</p>
|
||||
</div>
|
||||
<div class="account-panel__body">
|
||||
<dl class="account-kv">
|
||||
<div><dt>Name</dt><dd><?= htmlspecialchars(trim($profile['first_name'] . ' ' . $profile['last_name']), ENT_QUOTES) ?></dd></div>
|
||||
<div><dt>Anzeigename</dt><dd><?= htmlspecialchars($profile['display_name'], ENT_QUOTES) ?></dd></div>
|
||||
<div><dt>Ort</dt><dd><?= htmlspecialchars(trim($profile['city'] . ' ' . $profile['zip']), ENT_QUOTES) ?></dd></div>
|
||||
<div><dt>E-Mail</dt><dd><?= htmlspecialchars($profile['email'], ENT_QUOTES) ?></dd></div>
|
||||
<div><dt>Telefon</dt><dd><?= htmlspecialchars($profile['contact_phone'], ENT_QUOTES) ?></dd></div>
|
||||
<div><dt>Beruf</dt><dd><?= htmlspecialchars($profile['profession'], ENT_QUOTES) ?></dd></div>
|
||||
<div><dt>Sprachen</dt><dd><?= htmlspecialchars($profile['languages'], ENT_QUOTES) ?></dd></div>
|
||||
<div><dt>Über dich</dt><dd><?= htmlspecialchars($profile['about'], ENT_QUOTES) ?></dd></div>
|
||||
</dl>
|
||||
<div class="flex gap-12" style="margin-top:18px;">
|
||||
<button class="btn" type="button" data-modal-open="modalProfile">Profil bearbeiten</button>
|
||||
</div>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
<details style="margin-top:12px;">
|
||||
<summary style="cursor:pointer;">Vergangene Events anzeigen</summary>
|
||||
<?php if (!$eventsPast): ?>
|
||||
<p class="muted small">Keine vergangenen Events.</p>
|
||||
<?php else: ?>
|
||||
<ul class="dash-list" style="margin-top:10px;">
|
||||
<?php foreach ($eventsPast as $e): ?>
|
||||
<li>
|
||||
<div style="display:flex; justify-content:space-between; gap:12px; align-items:center; flex-wrap: wrap;">
|
||||
<div>
|
||||
<strong><?= htmlspecialchars($e['title'], ENT_QUOTES) ?></strong>
|
||||
<?php if ($e['status'] === 'cancelled'): ?>
|
||||
<span class="badge" style="background:#fee2e2; color:#991b1b;">Abgesagt</span>
|
||||
<?php endif; ?>
|
||||
<div class="muted small" style="margin-top:4px;">
|
||||
<?= htmlspecialchars($e['city'], ENT_QUOTES) ?> · <?= htmlspecialchars($e['starts_at'], ENT_QUOTES) ?> · <?= htmlspecialchars($e['visibility'], ENT_QUOTES) ?>
|
||||
· Anmeldungen: <?= (int)$e['participant_count'] ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
</details>
|
||||
|
||||
<?php if ($section === 'children'): ?>
|
||||
<section class="account-panel">
|
||||
<div class="account-panel__head">
|
||||
<h2>Deine Kinder</h2>
|
||||
<p class="muted">Optionale Angaben, die dir bei Planung und passenden Treffen helfen können.</p>
|
||||
</div>
|
||||
<div class="account-panel__body">
|
||||
<?php if (!$children): ?>
|
||||
<p class="muted small">Noch keine Kinder eingetragen.</p>
|
||||
<?php else: ?>
|
||||
<ul class="dash-list">
|
||||
<?php foreach ($children as $c): ?>
|
||||
<li><?= htmlspecialchars($c['first_name'], ENT_QUOTES) ?>, <?= htmlspecialchars($c['gender'], ENT_QUOTES) ?> <?= $c['age_years'] ? '(' . (int)$c['age_years'] . ' Jahre)' : '' ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
<div class="flex gap-12" style="margin-top:18px;">
|
||||
<button class="btn" type="button" data-modal-open="modalChild">Kind hinzufügen</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($section === 'community'): ?>
|
||||
<section class="account-panel">
|
||||
<div class="account-panel__head">
|
||||
<h2>Dein Community-Status</h2>
|
||||
<p class="muted">Rang, Punkte, Rollen und dein aktueller Stand in der Community.</p>
|
||||
</div>
|
||||
<div class="account-panel__body">
|
||||
<dl class="account-kv">
|
||||
<div><dt>Rang</dt><dd><?= htmlspecialchars(trim(($communityLevel['icon'] ?? '') . ' ' . ($communityLevel['label'] ?? '')), ENT_QUOTES) ?></dd></div>
|
||||
<div><dt>Punkte</dt><dd><?= number_format((float)$communityPoints, 1, ',', '.') ?></dd></div>
|
||||
<div><dt>Rollen</dt><dd><?= htmlspecialchars($communityRoles ? implode(', ', $communityRoles) : 'Keine besonderen Rollen', ENT_QUOTES) ?></dd></div>
|
||||
<div><dt>Themen erstellen</dt><dd><?= $communityRestrictions['thread_create_blocked'] ? 'Gesperrt' : 'Erlaubt' ?></dd></div>
|
||||
<div><dt>Antworten schreiben</dt><dd><?= $communityRestrictions['reply_blocked'] ? 'Gesperrt' : 'Erlaubt' ?></dd></div>
|
||||
</dl>
|
||||
<?php if (!empty($communityRestrictions['reason'])): ?>
|
||||
<p class="muted small" style="margin-top:14px;">Hinweis zur Community-Sperre: <?= htmlspecialchars((string)$communityRestrictions['reason'], ENT_QUOTES) ?></p>
|
||||
<?php endif; ?>
|
||||
<?php if ($communityCanApply): ?>
|
||||
<form method="post" class="stack gap-6" style="margin-top:18px;">
|
||||
<input type="hidden" name="action" value="community_admin_apply">
|
||||
<label class="label" for="communityMotivation">Bewerbung als Forum-Admin</label>
|
||||
<textarea id="communityMotivation" name="motivation" class="textarea" rows="4" placeholder="Warum möchtest du die Community als Forum-Admin unterstützen?" required></textarea>
|
||||
<button class="btn" type="submit">Bewerbung absenden</button>
|
||||
</form>
|
||||
<?php elseif ($communityApplication): ?>
|
||||
<div style="margin-top:18px;">
|
||||
<p class="muted small" style="margin:0;">Letzte Bewerbung: <strong><?= htmlspecialchars((string)$communityApplication['status'], ENT_QUOTES) ?></strong></p>
|
||||
<?php if (!empty($communityApplication['decision_reason'])): ?>
|
||||
<p class="muted small" style="margin-top:6px;"><?= htmlspecialchars((string)$communityApplication['decision_reason'], ENT_QUOTES) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($section === 'events'): ?>
|
||||
<section class="account-panel" id="events">
|
||||
<div class="account-panel__head">
|
||||
<h2>Deine Termine</h2>
|
||||
<p class="muted">Erstelle, bearbeite und verwalte deine eigenen Treffen.</p>
|
||||
</div>
|
||||
<div class="account-panel__body">
|
||||
<div class="flex gap-12" style="margin:0 0 16px 0; flex-wrap: wrap;">
|
||||
<button class="btn" type="button" data-modal-open="modalEvent">Event anlegen</button>
|
||||
</div>
|
||||
<?php if (!$eventsUpcoming): ?>
|
||||
<p class="muted small">Keine zukünftigen Events angelegt.</p>
|
||||
<?php else: ?>
|
||||
<ul class="dash-list" style="margin-top:10px;">
|
||||
<?php foreach ($eventsUpcoming as $e): ?>
|
||||
<li>
|
||||
<div style="display:flex; justify-content:space-between; gap:12px; align-items:center; flex-wrap: wrap;">
|
||||
<div>
|
||||
<strong><?= htmlspecialchars($e['title'], ENT_QUOTES) ?></strong>
|
||||
<?php if ($e['status'] === 'cancelled'): ?>
|
||||
<span class="badge" style="background:#fee2e2; color:#991b1b;">Abgesagt</span>
|
||||
<?php endif; ?>
|
||||
<div class="muted small" style="margin-top:4px;">
|
||||
<?= htmlspecialchars($e['city'], ENT_QUOTES) ?> · <?= htmlspecialchars($e['starts_at'], ENT_QUOTES) ?> · <?= htmlspecialchars($e['visibility'], ENT_QUOTES) ?>
|
||||
· Anmeldungen: <?= (int)$e['participant_count'] ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-8" style="flex-wrap: wrap;">
|
||||
<a class="btn ghost" href="/dashboard?section=events&edit_event=<?= (int)$e['id'] ?>#events">Bearbeiten</a>
|
||||
<?php if ((int)$e['participant_count'] === 0): ?>
|
||||
<form method="post" action="/dashboard?section=events#events" onsubmit="return confirm('Event wirklich löschen?');">
|
||||
<input type="hidden" name="action" value="event_delete">
|
||||
<input type="hidden" name="event_id" value="<?= (int)$e['id'] ?>">
|
||||
<button class="btn ghost" type="submit">Löschen</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<?php if ($e['status'] !== 'cancelled'): ?>
|
||||
<form method="post" action="/dashboard?section=events#events" onsubmit="return confirm('Event für alle absagen?');">
|
||||
<input type="hidden" name="action" value="event_cancel">
|
||||
<input type="hidden" name="event_id" value="<?= (int)$e['id'] ?>">
|
||||
<button class="btn ghost" type="submit">Absagen</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
<details style="margin-top:12px;">
|
||||
<summary style="cursor:pointer;">Vergangene Events anzeigen</summary>
|
||||
<?php if (!$eventsPast): ?>
|
||||
<p class="muted small">Keine vergangenen Events.</p>
|
||||
<?php else: ?>
|
||||
<ul class="dash-list" style="margin-top:10px;">
|
||||
<?php foreach ($eventsPast as $e): ?>
|
||||
<li>
|
||||
<div style="display:flex; justify-content:space-between; gap:12px; align-items:center; flex-wrap: wrap;">
|
||||
<div>
|
||||
<strong><?= htmlspecialchars($e['title'], ENT_QUOTES) ?></strong>
|
||||
<?php if ($e['status'] === 'cancelled'): ?>
|
||||
<span class="badge" style="background:#fee2e2; color:#991b1b;">Abgesagt</span>
|
||||
<?php endif; ?>
|
||||
<div class="muted small" style="margin-top:4px;">
|
||||
<?= htmlspecialchars($e['city'], ENT_QUOTES) ?> · <?= htmlspecialchars($e['starts_at'], ENT_QUOTES) ?> · <?= htmlspecialchars($e['visibility'], ENT_QUOTES) ?>
|
||||
· Anmeldungen: <?= (int)$e['participant_count'] ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</details>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($section === 'settings'): ?>
|
||||
<section class="account-panel">
|
||||
<div class="account-panel__head">
|
||||
<h2>Einstellungen</h2>
|
||||
<p class="muted">Standort, Browserfreigabe und Cookie-Einstellungen an einem Ort.</p>
|
||||
</div>
|
||||
<div class="account-panel__body">
|
||||
<form method="post" class="stack gap-12">
|
||||
<input type="hidden" name="action" value="settings_location">
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pLocationPref">Standort auf Papa-Kind-Treff verwenden</label>
|
||||
<select id="pLocationPref" name="location_tracking_preference" class="select">
|
||||
<option value="disabled" <?= (($profile['location_tracking_preference'] ?? 'prompt') === 'disabled') ? 'selected' : '' ?>>Nie verwenden</option>
|
||||
<option value="prompt" <?= (($profile['location_tracking_preference'] ?? 'prompt') === 'prompt') ? 'selected' : '' ?>>Bei Bedarf nachfragen</option>
|
||||
<option value="enabled" <?= (($profile['location_tracking_preference'] ?? 'prompt') === 'enabled') ? 'selected' : '' ?>>Wenn im Browser erlaubt, automatisch verwenden</option>
|
||||
</select>
|
||||
<p class="muted small">Diese Einstellung steuert nur, ob Papa-Kind-Treff Standortdaten nutzen darf. Die eigentliche Browserfreigabe wird separat im Browser verwaltet.</p>
|
||||
</div>
|
||||
<div class="flex gap-12">
|
||||
<button class="btn" type="submit">Einstellungen speichern</button>
|
||||
<button class="btn ghost" type="button" data-consent-open>Cookie-Einstellungen öffnen</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="card" style="margin-top:18px;" id="locationBrowserStatusCard">
|
||||
<strong>Browser-Status für Standort</strong>
|
||||
<p class="muted small" id="locationBrowserStatusText" style="margin:8px 0 0;">Wird geprüft …</p>
|
||||
<p class="muted small" id="locationBrowserStatusHint" style="margin:8px 0 0;">
|
||||
Deine Website-Einstellung und die Browserfreigabe sind getrennte Ebenen. Wenn der Browser blockiert, kann Papa-Kind-Treff den Standort auch bei aktiver Einstellung nicht nutzen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="settings-help" style="margin-top:18px;">
|
||||
<details>
|
||||
<summary>Warum gibt es zwei Einstellungen?</summary>
|
||||
<p class="muted small" style="margin:10px 0 0;">Der Browser entscheidet technisch, ob Standortzugriffe erlaubt sind. Papa-Kind-Treff entscheidet mit deiner Website-Einstellung, ob diese Möglichkeit auf der Plattform überhaupt verwendet werden soll.</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Chrome und Edge</summary>
|
||||
<p class="muted small" style="margin:10px 0 0;">Auf das Schloss-Symbol links neben der Adresszeile klicken, dann `Website-Einstellungen` oder `Standort` öffnen und die Freigabe dort anpassen.</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Firefox</summary>
|
||||
<p class="muted small" style="margin:10px 0 0;">Auf das Schloss-Symbol links neben der Adresse klicken und unter Berechtigungen den Standort prüfen oder zurücksetzen. Alternativ über Einstellungen → Datenschutz & Sicherheit → Berechtigungen.</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Safari</summary>
|
||||
<p class="muted small" style="margin:10px 0 0;">In Safari die Website-Einstellungen für den aktuellen Tab oder die Datenschutz-/Standort-Einstellungen des Browsers beziehungsweise des Geräts prüfen.</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Direkt in die Browser-Einstellungen springen?</summary>
|
||||
<p class="muted small" style="margin:10px 0 0;">Nein, das kann eine Website nicht zuverlässig und browserübergreifend auslösen. Deshalb zeigt Papa-Kind-Treff nur den Status an und erklärt, wo du die Freigabe findest.</p>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($section === 'admin' && in_array('admin', $allowedSections ?? [], true)): ?>
|
||||
<section class="account-panel">
|
||||
<div class="account-panel__head">
|
||||
<h2>Community-Verwaltung</h2>
|
||||
<p class="muted">Moderation, Bewerbungen, Rollen und Migration.</p>
|
||||
</div>
|
||||
<div class="account-panel__body">
|
||||
<div class="flex gap-12" style="margin:0 0 16px 0; flex-wrap:wrap;">
|
||||
<a class="btn" href="/community-admin">Zum Admin-Bereich</a>
|
||||
</div>
|
||||
<?php if ($communityMigrationStatus): ?>
|
||||
<ul class="dash-list">
|
||||
<li>Status: <?= !empty($communityMigrationStatus['complete']) ? 'Vollständig eingerichtet' : 'Migration noch offen' ?></li>
|
||||
<li>Fehlende Bausteine: <?= !empty($communityMigrationStatus['missing']) ? htmlspecialchars(implode(', ', $communityMigrationStatus['missing']), ENT_QUOTES) : 'Keine' ?></li>
|
||||
</ul>
|
||||
<?php else: ?>
|
||||
<p class="muted small">Migrationsstatus konnte nicht ermittelt werden.</p>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($communityIsSiteAdmin)): ?>
|
||||
<form method="post" style="margin-top:12px;">
|
||||
<input type="hidden" name="action" value="community_migrate">
|
||||
<button class="btn" type="submit">Community-Migration ausführen</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -263,15 +368,6 @@ $allowNoKidsChecked = $editEvent ? ((int)$editEvent['allow_kids'] === 0) : false
|
||||
<label class="label" for="pProf">Beruf</label>
|
||||
<input id="pProf" name="profession" class="input" value="<?= htmlspecialchars($profile['profession'], ENT_QUOTES) ?>">
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pLocationPref">Standortfreigabe</label>
|
||||
<select id="pLocationPref" name="location_tracking_preference" class="select">
|
||||
<option value="disabled" <?= (($profile['location_tracking_preference'] ?? 'prompt') === 'disabled') ? 'selected' : '' ?>>Deaktiviert</option>
|
||||
<option value="prompt" <?= (($profile['location_tracking_preference'] ?? 'prompt') === 'prompt') ? 'selected' : '' ?>>Beim nächsten Mal fragen</option>
|
||||
<option value="enabled" <?= (($profile['location_tracking_preference'] ?? 'prompt') === 'enabled') ? 'selected' : '' ?>>Immer verwenden</option>
|
||||
</select>
|
||||
<p class="muted small">Bei aktiver Freigabe wird dein aktueller Standort für passende Treffen in deiner Nähe verwendet. Du kannst die Abfrage hier jederzeit wieder auf Nachfrage oder komplett aus stellen.</p>
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pAbout">Kurzvorstellung</label>
|
||||
<textarea id="pAbout" name="about" class="textarea" rows="3"><?= htmlspecialchars($profile['about'], ENT_QUOTES) ?></textarea>
|
||||
@@ -335,7 +431,7 @@ $allowNoKidsChecked = $editEvent ? ((int)$editEvent['allow_kids'] === 0) : false
|
||||
<h3><?= $editing ? 'Event bearbeiten' : 'Neues Event' ?></h3>
|
||||
<button class="btn ghost" type="button" data-modal-close>✕</button>
|
||||
</div>
|
||||
<form class="stack gap-12" style="margin-top: 10px;" method="post" action="/dashboard#events">
|
||||
<form class="stack gap-12" style="margin-top: 10px;" method="post" action="/dashboard?section=events#events">
|
||||
<input type="hidden" name="action" value="<?= htmlspecialchars($actionEvent, ENT_QUOTES) ?>">
|
||||
<?php if ($editing && $editEvent): ?>
|
||||
<input type="hidden" name="event_id" value="<?= (int)$editEvent['id'] ?>">
|
||||
@@ -600,4 +696,45 @@ $allowNoKidsChecked = $editEvent ? ((int)$editEvent['allow_kids'] === 0) : false
|
||||
if (modal) { modal.classList.add('open'); }
|
||||
})();
|
||||
<?php endif; ?>
|
||||
|
||||
(function(){
|
||||
const statusText = document.getElementById('locationBrowserStatusText');
|
||||
const hintText = document.getElementById('locationBrowserStatusHint');
|
||||
const profilePreference = document.body?.dataset.locationPreference || 'prompt';
|
||||
|
||||
if (!statusText || !hintText) return;
|
||||
|
||||
const profileLabels = {
|
||||
disabled: 'Deaktiviert',
|
||||
prompt: 'Beim nächsten Mal fragen',
|
||||
enabled: 'Immer verwenden',
|
||||
};
|
||||
|
||||
const render = (browserState) => {
|
||||
if (browserState === 'granted') {
|
||||
statusText.textContent = 'Der Browser erlaubt Standortzugriffe derzeit.';
|
||||
} else if (browserState === 'denied') {
|
||||
statusText.textContent = 'Der Browser blockiert Standortzugriffe derzeit.';
|
||||
} else if (browserState === 'prompt') {
|
||||
statusText.textContent = 'Der Browser fragt bei Standortzugriffen erneut nach.';
|
||||
} else {
|
||||
statusText.textContent = 'Der Browserstatus konnte nicht eindeutig ermittelt werden.';
|
||||
}
|
||||
|
||||
const profileState = profileLabels[profilePreference] || profileLabels.prompt;
|
||||
hintText.textContent = `Website-Einstellung: ${profileState}. Wenn Browser und Website-Einstellung sich unterscheiden, gilt technisch zuerst die Browserfreigabe.`;
|
||||
};
|
||||
|
||||
if (!('permissions' in navigator) || typeof navigator.permissions.query !== 'function') {
|
||||
render('unknown');
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.permissions.query({ name: 'geolocation' }).then((result) => {
|
||||
render(result.state);
|
||||
if (typeof result.onchange !== 'undefined') {
|
||||
result.onchange = () => render(result.state);
|
||||
}
|
||||
}).catch(() => render('unknown'));
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -116,6 +116,7 @@ if ($pdo && ($q !== '' || $loc !== '' || ($lat !== null && $lng !== null))) {
|
||||
</main>
|
||||
<script>
|
||||
(function(){
|
||||
const body = document.body;
|
||||
const locInput = document.getElementById('loc');
|
||||
const latInput = document.getElementById('lat');
|
||||
const lngInput = document.getElementById('lng');
|
||||
@@ -125,8 +126,64 @@ if ($pdo && ($q !== '' || $loc !== '' || ($lat !== null && $lng !== null))) {
|
||||
const btnMap = document.getElementById('btnMap');
|
||||
const mapWrapper = document.getElementById('mapWrapper');
|
||||
const mapContainer = document.getElementById('mapContainer');
|
||||
const isLoggedIn = body?.dataset.auth === '1';
|
||||
const profileLocationPreference = body?.dataset.locationPreference || 'prompt';
|
||||
const locationStorageKey = 'pkt_user_location';
|
||||
const locationSessionStorageKey = 'pkt_user_location_session';
|
||||
let map, marker;
|
||||
|
||||
function effectiveLocationPreference() {
|
||||
if (!isLoggedIn) {
|
||||
try {
|
||||
const guestPreference = window.localStorage.getItem('pkt_location_preference_guest');
|
||||
if (guestPreference && ['disabled', 'prompt', 'enabled'].includes(guestPreference)) {
|
||||
return guestPreference;
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return profileLocationPreference;
|
||||
}
|
||||
|
||||
function getStoredLocation() {
|
||||
const candidates = [];
|
||||
try {
|
||||
const sessionValue = window.sessionStorage.getItem(locationSessionStorageKey);
|
||||
if (sessionValue) candidates.push(sessionValue);
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
const localValue = window.localStorage.getItem(locationStorageKey);
|
||||
if (localValue) candidates.push(localValue);
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
for (const value of candidates) {
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
if (parsed && Number.isFinite(parsed.lat) && Number.isFinite(parsed.lng)) {
|
||||
return parsed;
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function applyLocation(lat, lng, label = 'Mein Standort') {
|
||||
latInput.value = Number(lat).toFixed(6);
|
||||
lngInput.value = Number(lng).toFixed(6);
|
||||
if (locInput && !locInput.value.trim()) {
|
||||
locInput.value = label;
|
||||
}
|
||||
toggleRadius(true);
|
||||
}
|
||||
|
||||
function toggleRadius(show) {
|
||||
if (!radiusWrap) return;
|
||||
radiusWrap.hidden = !show;
|
||||
@@ -198,17 +255,25 @@ if ($pdo && ($q !== '' || $loc !== '' || ($lat !== null && $lng !== null))) {
|
||||
window.PKTConsent?.openPreferences?.();
|
||||
return;
|
||||
}
|
||||
|
||||
const preference = effectiveLocationPreference();
|
||||
if (preference === 'disabled') {
|
||||
alert('Die Standortverwendung ist in deinem Profil deaktiviert.');
|
||||
return;
|
||||
}
|
||||
|
||||
const storedLocation = getStoredLocation();
|
||||
if (preference === 'enabled' && storedLocation) {
|
||||
applyLocation(storedLocation.lat, storedLocation.lng, storedLocation.label || 'Mein Standort');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!navigator.geolocation) {
|
||||
alert('Geolocation wird nicht unterstützt.');
|
||||
return;
|
||||
}
|
||||
navigator.geolocation.getCurrentPosition((pos) => {
|
||||
const lat = pos.coords.latitude;
|
||||
const lng = pos.coords.longitude;
|
||||
latInput.value = lat.toFixed(6);
|
||||
lngInput.value = lng.toFixed(6);
|
||||
if (locInput && !locInput.value.trim()) locInput.value = 'Mein Standort';
|
||||
toggleRadius(true);
|
||||
applyLocation(pos.coords.latitude, pos.coords.longitude, 'Mein Standort');
|
||||
}, () => alert('Standort konnte nicht ermittelt werden.'));
|
||||
});
|
||||
|
||||
|
||||
@@ -45,8 +45,11 @@ if ($isLoggedIn) {
|
||||
<span class="profile-menu__name"><?= htmlspecialchars($displayName, ENT_QUOTES) ?></span>
|
||||
</button>
|
||||
<div class="profile-menu__dropdown" role="menu">
|
||||
<a href="/dashboard" role="menuitem">Übersicht</a>
|
||||
<a href="/dashboard" role="menuitem">Mitgliederbereich</a>
|
||||
<a href="/dashboard?section=profile" role="menuitem">Profil</a>
|
||||
<a href="/dashboard?section=children" role="menuitem">Kinder</a>
|
||||
<a href="/dashboard?section=events" role="menuitem">Termine</a>
|
||||
<a href="/dashboard?section=community" role="menuitem">Community</a>
|
||||
<a href="/dashboard?section=settings" role="menuitem">Einstellungen</a>
|
||||
<a href="/logout" role="menuitem">Abmelden</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,8 +65,11 @@ if ($isLoggedIn) {
|
||||
<a href="/search">Suche</a>
|
||||
<a href="/community">Community</a>
|
||||
<?php if ($isLoggedIn): ?>
|
||||
<a class="btn ghost" href="/dashboard">Übersicht</a>
|
||||
<a class="btn ghost" href="/dashboard">Mitgliederbereich</a>
|
||||
<a class="btn ghost" href="/dashboard?section=profile">Profil</a>
|
||||
<a class="btn ghost" href="/dashboard?section=children">Kinder</a>
|
||||
<a class="btn ghost" href="/dashboard?section=events">Termine</a>
|
||||
<a class="btn ghost" href="/dashboard?section=community">Community</a>
|
||||
<a class="btn ghost" href="/dashboard?section=settings">Einstellungen</a>
|
||||
<a class="btn block" href="/logout">Abmelden</a>
|
||||
<?php else: ?>
|
||||
<a class="btn ghost" href="/login">Anmelden</a>
|
||||
|
||||
@@ -207,6 +207,27 @@ body {
|
||||
.forum-sidebar__group a:hover { background: #f8f4ec; color: var(--color-primary); }
|
||||
.forum-sidebar__group a.is-active { background: var(--color-primary); color: var(--color-primary-contrast); }
|
||||
.forum-sidebar__group small { font-size: 12px; opacity: .8; }
|
||||
.account-layout { display: grid; grid-template-columns: 290px minmax(0, 1fr); gap: 24px; align-items: start; }
|
||||
.account-sidebar { position: sticky; top: 104px; }
|
||||
.account-sidebar__inner { background: #fff; border: 1px solid var(--color-border); border-radius: var(--radius-lg); box-shadow: var(--shadow-card); overflow: hidden; }
|
||||
.account-sidebar__head { padding: 20px 20px 16px; border-bottom: 1px solid var(--color-border); background: linear-gradient(180deg, #fffdfa, #f8f4ec); }
|
||||
.account-sidebar__head h2 { margin: 10px 0 0; font-size: 22px; }
|
||||
.account-sidebar__nav { padding: 14px; display: grid; gap: 6px; }
|
||||
.account-sidebar__nav a { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 12px; border-radius: 12px; color: var(--color-text); font-weight: 600; }
|
||||
.account-sidebar__nav a:hover { background: #f8f4ec; color: var(--color-primary); }
|
||||
.account-sidebar__nav a.is-active { background: var(--color-primary); color: var(--color-primary-contrast); }
|
||||
.account-main { min-width: 0; display: grid; gap: 20px; }
|
||||
.account-panel { background: #fff; border: 1px solid var(--color-border); border-radius: var(--radius-lg); box-shadow: var(--shadow-card); overflow: hidden; }
|
||||
.account-panel__head { padding: 22px 24px; border-bottom: 1px solid var(--color-border); background: #fffdfa; }
|
||||
.account-panel__head h2 { margin: 0 0 6px; }
|
||||
.account-panel__body { padding: 24px; }
|
||||
.account-kv { display: grid; gap: 10px; margin: 0; }
|
||||
.account-kv div { display: grid; grid-template-columns: 190px minmax(0, 1fr); gap: 14px; }
|
||||
.account-kv dt { font-weight: 700; color: var(--color-muted); }
|
||||
.account-kv dd { margin: 0; }
|
||||
.settings-help { display: grid; gap: 14px; }
|
||||
.settings-help details { border: 1px solid var(--color-border); border-radius: 12px; padding: 12px 14px; background: #fff; }
|
||||
.settings-help summary { cursor: pointer; font-weight: 700; }
|
||||
.forum-search { display: grid; gap: 8px; min-width: min(100%, 360px); }
|
||||
.forum-search--hero { width: 100%; }
|
||||
.forum-search__row { display: flex; gap: 10px; }
|
||||
@@ -271,7 +292,9 @@ body {
|
||||
.forum-list__header,
|
||||
.forum-row { grid-template-columns: minmax(0, 1fr); }
|
||||
.forum-shell { grid-template-columns: 1fr; }
|
||||
.account-layout { grid-template-columns: 1fr; }
|
||||
.forum-sidebar { position: static; }
|
||||
.account-sidebar { position: static; }
|
||||
.forum-row__count,
|
||||
.forum-row__activity { grid-auto-flow: column; justify-content: start; gap: 10px; align-items: baseline; }
|
||||
.forum-post { grid-template-columns: 1fr; }
|
||||
@@ -295,9 +318,11 @@ body {
|
||||
.forum-board-row,
|
||||
.forum-admin-item { padding-left: 16px; padding-right: 16px; }
|
||||
.forum-post__body,
|
||||
.forum-reply-form { padding: 18px 16px; }
|
||||
.forum-reply-form,
|
||||
.account-panel__body { padding: 18px 16px; }
|
||||
.cookie-consent__inner { grid-template-columns: 1fr; }
|
||||
.cookie-consent__actions { justify-content: stretch; }
|
||||
.account-kv div { grid-template-columns: 1fr; gap: 4px; }
|
||||
}
|
||||
|
||||
/* Auth & Dashboard */
|
||||
|
||||
@@ -159,6 +159,8 @@ final class AccountPages
|
||||
$communityAccess = $pdo ? new CommunityAccess($pdo, $communityConfig) : null;
|
||||
$communityMigration = $pdo ? new CommunityMigration($pdo) : null;
|
||||
$profileSettings = $pdo ? new ProfileSettings($pdo) : null;
|
||||
$section = (string)($_GET['section'] ?? 'profile');
|
||||
$allowedSections = ['profile', 'children', 'events', 'community', 'settings'];
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = $_POST['action'] ?? '';
|
||||
@@ -188,6 +190,15 @@ final class AccountPages
|
||||
'id' => $userId,
|
||||
]);
|
||||
$info = 'Profil gespeichert.';
|
||||
} elseif ($action === 'settings_location') {
|
||||
$locationPreference = (string)($_POST['location_tracking_preference'] ?? 'prompt');
|
||||
if ($profileSettings) {
|
||||
$profileSettings->updateLocationTrackingPreference(
|
||||
$userId,
|
||||
in_array($locationPreference, ['disabled', 'prompt', 'enabled'], true) ? $locationPreference : 'prompt'
|
||||
);
|
||||
}
|
||||
$info = 'Einstellungen gespeichert.';
|
||||
} elseif ($action === 'child_add') {
|
||||
$firstNameEnc = $crypto ? $crypto->encrypt(trim((string)$_POST['first_name'])) : trim((string)$_POST['first_name']);
|
||||
$noteEnc = $crypto ? $crypto->encrypt(trim((string)$_POST['note'])) : trim((string)$_POST['note']);
|
||||
@@ -396,6 +407,12 @@ final class AccountPages
|
||||
];
|
||||
$communityIsSiteAdmin = $communityAccess ? $communityAccess->canManageApplications($userId) : false;
|
||||
$communityMigrationStatus = ($communityMigration && $communityIsSiteAdmin) ? $communityMigration->status() : null;
|
||||
if (in_array('forum_admin', $communityRoles, true) || $communityIsSiteAdmin) {
|
||||
$allowedSections[] = 'admin';
|
||||
}
|
||||
if (!in_array($section, $allowedSections, true)) {
|
||||
$section = 'profile';
|
||||
}
|
||||
|
||||
return compact(
|
||||
'flash',
|
||||
@@ -413,7 +430,9 @@ final class AccountPages
|
||||
'communityCanApply',
|
||||
'communityRestrictions',
|
||||
'communityIsSiteAdmin',
|
||||
'communityMigrationStatus'
|
||||
'communityMigrationStatus',
|
||||
'section',
|
||||
'allowedSections'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user