This commit is contained in:
@@ -20,6 +20,7 @@ Aktueller Projektstand
|
||||
- Der Hinweis auf die verschluesselte Speicherung personenbezogener Profilangaben ist auf der Datenschutz-Seite jetzt zusaetzlich deutlich hervorgehoben.
|
||||
- Das Profil besitzt jetzt zusaetzlich eine vollstaendige Adresse mit verschluesselter Strasse/Hausnummer sowie eine optionale Adress-Lokalisierung ueber die vorhandenen externen Karten-/Geocoding-Dienste.
|
||||
- Die Profiladresse unterstuetzt jetzt ausserdem eine Auswahl moeglicher Adress-Treffer, die Uebernahme aus dem Browser-Standort und einen kurzen Hinweisdialog zur lokalen Relevanz.
|
||||
- Der Profil-Editor im Mitgliederbereich ist jetzt kompakter aufgebaut, in ausklappbare Bereiche gegliedert und enthaelt die bearbeitbare Konto-E-Mail direkt im Formular.
|
||||
- Fuer Umkreis- und Distanzfunktionen bleiben nur `lat`/`lng` technisch im Klartext; die eigentliche Strasse/Hausnummer ist verschluesselt.
|
||||
- Alte statische Avatar-Presets und frühere Layer-Assets sind entfernt.
|
||||
- Änderungen an Funktionen mit Cookies, LocalStorage, SessionStorage, Geolocation, Tracking oder Drittanbietern müssen immer auch in Datenschutz-/Cookie-Hinweisen und im Consent-Flow berücksichtigt werden.
|
||||
|
||||
@@ -40,6 +40,7 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter. Kernbereiche sind l
|
||||
- Der Hinweis auf die verschluesselte Speicherung personenbezogener Profilangaben ist auf der Datenschutz-Seite jetzt zusaetzlich deutlich hervorgehoben.
|
||||
- Im Profil koennen jetzt auch Strasse/Hausnummer, PLZ, Ort und Region gepflegt werden; fuer die Adress-Lokalisierung werden die bereits vorhandenen externen Karten-/Geocoding-Dienste wiederverwendet.
|
||||
- Die Profiladresse bietet jetzt zusaetzlich eine Trefferauswahl fuer die Suche, eine Uebernahme aus dem Browser-Standort und einen kurzen Hinweisdialog zur Relevanz fuer lokale Termin-Hinweise.
|
||||
- Der Profil-Editor ist jetzt kompakter gegliedert, nutzt ausklappbare Bereiche fuer Basisdaten, Adresse, Sprache/Beruf und Kurzvorstellung und enthaelt die bearbeitbare Konto-E-Mail direkt im Formular.
|
||||
- Im Profil kann der Nutzer aktuell Komponenten wie Augen, Augenbrauen, Mund, Brille, Haare, Bart und Ohrringe direkt per Vorschaubild auswählen.
|
||||
- Der Button `Random Profilbild` setzt Seed und Varianten des aktiven Styles zufaellig neu und aktualisiert die Vorschau sofort.
|
||||
- Beim Oeffnen des Avatar-Generators wird nur der aktive Style-/Komponenten-Tab geladen; weitere Varianten werden erst beim Wechsel nachgeladen.
|
||||
|
||||
@@ -34,6 +34,7 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf lokale
|
||||
- der Hinweis auf die verschluesselte Speicherung personenbezogener Profilangaben ist jetzt auch auf der Datenschutz-Seite deutlich hervorgehoben
|
||||
- das Profil unterstuetzt jetzt eine vollstaendige Adresse mit verschluesselter Strasse/Hausnummer sowie optionaler Adress-Lokalisierung ueber die bereits vorhandenen Karten-/Geocoding-Dienste
|
||||
- bei der Profiladresse gibt es jetzt eine Trefferauswahl fuer Adresssuche, eine Uebernahme aus dem Browser-Standort sowie einen mobilen Hinweisdialog, warum die Adresse fuer lokale Termine relevant ist
|
||||
- der Profil-Editor im Mitgliederbereich ist jetzt kompakter gruppiert, nutzt ausklappbare Abschnitte und enthaelt die bearbeitbare Konto-E-Mail wieder direkt im Formular
|
||||
- Debug-Floating-Button nur für `site_admin` im Debug-Modus
|
||||
|
||||
## Technik
|
||||
|
||||
@@ -78,16 +78,31 @@ $sectionLinks = [
|
||||
<button class="btn" type="button" data-modal-open="modalAvatar">Profilbild gestalten</button>
|
||||
</div>
|
||||
</div>
|
||||
<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>Adresse</dt><dd><?= htmlspecialchars(trim(implode(', ', array_filter([(string)($profile['street'] ?? ''), trim((string)$profile['zip'] . ' ' . (string)$profile['city']), (string)($profile['region'] ?? '')]))), 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="profile-summary-grid">
|
||||
<section class="profile-summary-card">
|
||||
<span class="profile-summary-card__eyebrow">Basis</span>
|
||||
<dl class="account-kv account-kv--compact">
|
||||
<div><dt>Anzeigename</dt><dd><?= htmlspecialchars($profile['display_name'] ?: 'Nicht hinterlegt', ENT_QUOTES) ?></dd></div>
|
||||
<div><dt>Name</dt><dd><?= htmlspecialchars(trim($profile['first_name'] . ' ' . $profile['last_name']) ?: 'Nicht hinterlegt', ENT_QUOTES) ?></dd></div>
|
||||
<div><dt>E-Mail</dt><dd><?= htmlspecialchars($profile['email'] ?: 'Nicht hinterlegt', ENT_QUOTES) ?></dd></div>
|
||||
<div><dt>Telefon</dt><dd><?= htmlspecialchars($profile['contact_phone'] ?: 'Nicht hinterlegt', ENT_QUOTES) ?></dd></div>
|
||||
</dl>
|
||||
</section>
|
||||
<section class="profile-summary-card">
|
||||
<span class="profile-summary-card__eyebrow">Standort</span>
|
||||
<dl class="account-kv account-kv--compact">
|
||||
<div><dt>Adresse</dt><dd><?= htmlspecialchars(trim(implode(', ', array_filter([(string)($profile['street'] ?? ''), trim((string)$profile['zip'] . ' ' . (string)$profile['city']), (string)($profile['region'] ?? '')]))) ?: 'Nicht hinterlegt', ENT_QUOTES) ?></dd></div>
|
||||
<div><dt>Sprachen</dt><dd><?= htmlspecialchars($profile['languages'] ?: 'Nicht hinterlegt', ENT_QUOTES) ?></dd></div>
|
||||
<div><dt>Beruf</dt><dd><?= htmlspecialchars($profile['profession'] ?: 'Nicht hinterlegt', ENT_QUOTES) ?></dd></div>
|
||||
</dl>
|
||||
</section>
|
||||
</div>
|
||||
<?php if (trim((string)$profile['about']) !== ''): ?>
|
||||
<div class="profile-about-card">
|
||||
<strong>Über dich</strong>
|
||||
<p><?= nl2br(htmlspecialchars((string)$profile['about'], ENT_QUOTES)) ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="flex gap-12" style="margin-top:18px;">
|
||||
<button class="btn" type="button" data-modal-open="modalProfile">Profil bearbeiten</button>
|
||||
</div>
|
||||
@@ -484,91 +499,116 @@ $sectionLinks = [
|
||||
<h3>Profil bearbeiten</h3>
|
||||
<button class="btn ghost" type="button" data-modal-close>✕</button>
|
||||
</div>
|
||||
<form method="post" class="stack gap-12">
|
||||
<form method="post" class="stack gap-12 profile-edit-form">
|
||||
<input type="hidden" name="action" value="profile">
|
||||
<input type="hidden" id="pRegion" name="region" value="<?= htmlspecialchars((string)($profile['region'] ?? ''), ENT_QUOTES) ?>">
|
||||
<input type="hidden" id="pLat" name="lat" value="<?= htmlspecialchars((string)($profile['lat'] ?? ''), ENT_QUOTES) ?>">
|
||||
<input type="hidden" id="pLng" name="lng" value="<?= htmlspecialchars((string)($profile['lng'] ?? ''), ENT_QUOTES) ?>">
|
||||
<div class="form-grid">
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pName">Anzeigename</label>
|
||||
<input id="pName" name="display_name" class="input" value="<?= htmlspecialchars($profile['display_name'], ENT_QUOTES) ?>">
|
||||
<p class="muted small" style="margin:0;">Persoenliche Angaben werden app-seitig verschluesselt gespeichert. Nur fuer Login und Kontofunktionen wird die E-Mail intern ueber einen separaten Lookup-Hash adressiert.</p>
|
||||
<details class="profile-edit-section" open>
|
||||
<summary>Basisdaten</summary>
|
||||
<div class="profile-edit-section__body">
|
||||
<div class="form-grid">
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pName">Anzeigename</label>
|
||||
<input id="pName" name="display_name" class="input" value="<?= htmlspecialchars($profile['display_name'], ENT_QUOTES) ?>">
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pEmail">E-Mail-Adresse</label>
|
||||
<input id="pEmail" name="email" type="email" class="input" value="<?= htmlspecialchars((string)$profile['email'], ENT_QUOTES) ?>" autocomplete="email" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pFirst">Vorname</label>
|
||||
<input id="pFirst" name="first_name" class="input" value="<?= htmlspecialchars($profile['first_name'], ENT_QUOTES) ?>" autocomplete="given-name">
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pLast">Nachname</label>
|
||||
<input id="pLast" name="last_name" class="input" value="<?= htmlspecialchars($profile['last_name'], ENT_QUOTES) ?>" autocomplete="family-name">
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pPhone">Telefon (mobil)</label>
|
||||
<input id="pPhone" name="contact_phone" class="input" value="<?= htmlspecialchars($profile['contact_phone'], ENT_QUOTES) ?>" autocomplete="tel">
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pFirst">Vorname</label>
|
||||
<input id="pFirst" name="first_name" class="input" value="<?= htmlspecialchars($profile['first_name'], ENT_QUOTES) ?>">
|
||||
</details>
|
||||
<details class="profile-edit-section" open>
|
||||
<summary>Adresse und Umgebung</summary>
|
||||
<div class="profile-edit-section__body">
|
||||
<div class="form-grid">
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pStreet">Straße / Hausnummer</label>
|
||||
<input id="pStreet" name="street" class="input" value="<?= htmlspecialchars((string)($profile['street'] ?? ''), ENT_QUOTES) ?>" placeholder="z. B. Musterstraße 12" autocomplete="street-address">
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pZip">PLZ</label>
|
||||
<input id="pZip" name="zip" class="input" value="<?= htmlspecialchars($profile['zip'], ENT_QUOTES) ?>" autocomplete="postal-code">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pCity">Ort</label>
|
||||
<input id="pCity" name="city" class="input" value="<?= htmlspecialchars($profile['city'], ENT_QUOTES) ?>" autocomplete="address-level2">
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pRegionView">Region / Bezirk</label>
|
||||
<input id="pRegionView" class="input" value="<?= htmlspecialchars((string)($profile['region'] ?? ''), ENT_QUOTES) ?>" placeholder="wird bei Lokalisierung ergänzt" autocomplete="address-level1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-12" style="flex-wrap:wrap;">
|
||||
<button class="btn ghost" type="button" id="btnProfileLocate">Adresse suchen</button>
|
||||
<button class="btn ghost" type="button" id="btnProfileBrowserLocation">Browser-Adresse übernehmen</button>
|
||||
<button class="btn ghost" type="button" data-modal-open="modalProfileAddressHelp" aria-label="Hilfe zur Profiladresse">?</button>
|
||||
</div>
|
||||
<p class="muted small" style="margin:0;">Für Suche und Browser-Übernahme werden dieselben Karten-/Adressdienste wie bei Events genutzt und nur nach Einwilligung in externe Dienste.</p>
|
||||
<div id="profileAddressStatus" class="muted small" style="margin:0;"></div>
|
||||
<div id="profileAddressSelectedLabel" class="muted small" style="margin:0;<?= empty($profile['street']) && empty($profile['city']) ? ' display:none;' : '' ?>">
|
||||
<?php if (!empty($profile['street']) || !empty($profile['city'])): ?>
|
||||
Gewaehlte Adresse: <?= htmlspecialchars(trim(implode(', ', array_filter([(string)($profile['street'] ?? ''), trim((string)$profile['zip'] . ' ' . (string)$profile['city']), (string)($profile['region'] ?? '')]))), ENT_QUOTES) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div id="profileAddressNeedsValidation" class="muted small" style="margin:0; display:none;">
|
||||
Die Adresse wurde manuell geaendert. Vor dem Speichern bitte erneut suchen oder die Browser-Adresse uebernehmen.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pStreet">Straße / Hausnummer</label>
|
||||
<input id="pStreet" name="street" class="input" value="<?= htmlspecialchars((string)($profile['street'] ?? ''), ENT_QUOTES) ?>" placeholder="z. B. Musterstraße 12">
|
||||
</details>
|
||||
<details class="profile-edit-section">
|
||||
<summary>Sprache und Beruf</summary>
|
||||
<div class="profile-edit-section__body">
|
||||
<div class="stack gap-6">
|
||||
<label class="label">Sprachen (Mehrfachauswahl)</label>
|
||||
<div class="chips" style="flex-wrap: wrap;">
|
||||
<?php
|
||||
$langOptions = ['Deutsch','Englisch','Französisch','Spanisch','Türkisch','Arabisch','Polnisch'];
|
||||
$currentLangs = array_filter(array_map('trim', explode(',', (string)$profile['languages'])));
|
||||
?>
|
||||
<?php foreach ($langOptions as $opt): ?>
|
||||
<label class="chip" style="cursor:pointer;">
|
||||
<input type="checkbox" name="languages[]" value="<?= htmlspecialchars($opt, ENT_QUOTES) ?>" <?= in_array($opt, $currentLangs, true) ? 'checked' : '' ?> style="margin-right:6px;">
|
||||
<?= htmlspecialchars($opt, ENT_QUOTES) ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<label class="label" for="pLangCustom">Weitere Sprachen (Kommagetrennt)</label>
|
||||
<input id="pLangCustom" name="languages[]" class="input" placeholder="z. B. Italienisch, Niederländisch">
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pProf">Beruf</label>
|
||||
<input id="pProf" name="profession" class="input" value="<?= htmlspecialchars($profile['profession'], ENT_QUOTES) ?>" autocomplete="organization-title">
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pLast">Nachname</label>
|
||||
<input id="pLast" name="last_name" class="input" value="<?= htmlspecialchars($profile['last_name'], ENT_QUOTES) ?>">
|
||||
</details>
|
||||
<details class="profile-edit-section">
|
||||
<summary>Kurzvorstellung</summary>
|
||||
<div class="profile-edit-section__body">
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pAbout">Über dich</label>
|
||||
<textarea id="pAbout" name="about" class="textarea" rows="4"><?= htmlspecialchars($profile['about'], ENT_QUOTES) ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pZip">PLZ</label>
|
||||
<input id="pZip" name="zip" class="input" value="<?= htmlspecialchars($profile['zip'], ENT_QUOTES) ?>">
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pCity">Ort</label>
|
||||
<input id="pCity" name="city" class="input" value="<?= htmlspecialchars($profile['city'], ENT_QUOTES) ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pRegionView">Region / Bezirk</label>
|
||||
<input id="pRegionView" class="input" value="<?= htmlspecialchars((string)($profile['region'] ?? ''), ENT_QUOTES) ?>" placeholder="wird bei Lokalisierung ergänzt">
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="pPhone">Telefon (mobil)</label>
|
||||
<input id="pPhone" name="contact_phone" class="input" value="<?= htmlspecialchars($profile['contact_phone'], ENT_QUOTES) ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-12" style="flex-wrap:wrap;">
|
||||
<button class="btn ghost" type="button" id="btnProfileLocate">Adresse suchen</button>
|
||||
<button class="btn ghost" type="button" id="btnProfileBrowserLocation">Browser-Adresse übernehmen</button>
|
||||
<button class="btn ghost" type="button" data-modal-open="modalProfileAddressHelp" aria-label="Hilfe zur Profiladresse">?</button>
|
||||
</div>
|
||||
<p class="muted small" style="margin:0;">Für Suche und Browser-Übernahme werden dieselben Karten-/Adressdienste wie bei Events genutzt und nur nach Einwilligung in externe Dienste.</p>
|
||||
<div id="profileAddressStatus" class="muted small" style="margin:0;"></div>
|
||||
<div id="profileAddressSelectedLabel" class="muted small" style="margin:0;<?= empty($profile['street']) && empty($profile['city']) ? ' display:none;' : '' ?>">
|
||||
<?php if (!empty($profile['street']) || !empty($profile['city'])): ?>
|
||||
Gewaehlte Adresse: <?= htmlspecialchars(trim(implode(', ', array_filter([(string)($profile['street'] ?? ''), trim((string)$profile['zip'] . ' ' . (string)$profile['city']), (string)($profile['region'] ?? '')]))), ENT_QUOTES) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div id="profileAddressNeedsValidation" class="muted small" style="margin:0; display:none;">
|
||||
Die Adresse wurde manuell geaendert. Vor dem Speichern bitte erneut suchen oder die Browser-Adresse uebernehmen.
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label">Sprachen (Mehrfachauswahl)</label>
|
||||
<div class="chips" style="flex-wrap: wrap;">
|
||||
<?php
|
||||
$langOptions = ['Deutsch','Englisch','Französisch','Spanisch','Türkisch','Arabisch','Polnisch'];
|
||||
$currentLangs = array_filter(array_map('trim', explode(',', (string)$profile['languages'])));
|
||||
?>
|
||||
<?php foreach ($langOptions as $opt): ?>
|
||||
<label class="chip" style="cursor:pointer;">
|
||||
<input type="checkbox" name="languages[]" value="<?= htmlspecialchars($opt, ENT_QUOTES) ?>" <?= in_array($opt, $currentLangs, true) ? 'checked' : '' ?> style="margin-right:6px;">
|
||||
<?= htmlspecialchars($opt, ENT_QUOTES) ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<label class="label" for="pLangCustom">Weitere Sprachen (Kommagetrennt)</label>
|
||||
<input id="pLangCustom" name="languages[]" class="input" placeholder="z. B. Italienisch, Niederländisch">
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<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="pAbout">Kurzvorstellung</label>
|
||||
<textarea id="pAbout" name="about" class="textarea" rows="3"><?= htmlspecialchars($profile['about'], ENT_QUOTES) ?></textarea>
|
||||
</div>
|
||||
</details>
|
||||
<div class="flex gap-12">
|
||||
<button class="btn ghost" type="button" data-modal-close>Abbrechen</button>
|
||||
<button class="btn" type="submit">Speichern</button>
|
||||
|
||||
@@ -241,12 +241,27 @@ body {
|
||||
.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--compact { gap: 8px; }
|
||||
.account-kv--compact div { grid-template-columns: 120px minmax(0, 1fr); gap: 10px; }
|
||||
.account-kv dt { font-weight: 700; color: var(--color-muted); }
|
||||
.account-kv dd { margin: 0; }
|
||||
.profile-avatar-panel { display: grid; grid-template-columns: 160px minmax(0, 1fr); gap: 22px; align-items: center; margin-bottom: 24px; padding: 20px; background: linear-gradient(135deg, #fffdfa, #f7f0e6); border: 1px solid var(--color-border); border-radius: 18px; }
|
||||
.profile-avatar-panel__preview { display: flex; justify-content: center; }
|
||||
.profile-avatar-panel__content h3 { margin: 0 0 8px; }
|
||||
.profile-avatar-panel__content p { margin: 0 0 14px; }
|
||||
.profile-summary-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
|
||||
.profile-summary-card { padding: 18px; background: #fffdfa; border: 1px solid var(--color-border); border-radius: 18px; }
|
||||
.profile-summary-card__eyebrow { display: inline-block; margin-bottom: 12px; font-size: 12px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--color-highlight); }
|
||||
.profile-about-card { margin-top: 16px; padding: 18px; background: linear-gradient(135deg, #fff, #f8f4ec); border: 1px solid var(--color-border); border-radius: 18px; }
|
||||
.profile-about-card strong { display: block; margin-bottom: 8px; }
|
||||
.profile-about-card p { margin: 0; line-height: 1.65; color: var(--color-text); }
|
||||
.profile-edit-form { max-width: 100%; }
|
||||
.profile-edit-section { border: 1px solid var(--color-border); border-radius: 18px; background: #fffdfa; overflow: hidden; }
|
||||
.profile-edit-section summary { cursor: pointer; list-style: none; padding: 16px 18px; font-weight: 800; color: var(--color-text); display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
||||
.profile-edit-section summary::-webkit-details-marker { display: none; }
|
||||
.profile-edit-section summary::after { content: "+"; width: 28px; height: 28px; border-radius: 999px; border: 1px solid var(--color-border); display: inline-flex; align-items: center; justify-content: center; color: var(--color-primary); background: #fff; font-size: 18px; line-height: 1; flex: 0 0 auto; }
|
||||
.profile-edit-section[open] summary::after { content: "−"; }
|
||||
.profile-edit-section__body { display: grid; gap: 14px; padding: 0 18px 18px; border-top: 1px solid rgba(196, 169, 120, 0.22); }
|
||||
.avatar-builder { height: 100%; min-height: 0; }
|
||||
.avatar-builder__shell { height: 100%; min-height: 0; display: grid; grid-template-rows: auto minmax(0, 1fr); gap: 14px; }
|
||||
.avatar-builder__top { display: grid; gap: 14px; padding: 14px; background: linear-gradient(180deg, #fffdfa, #f8f4ec); border: 1px solid var(--color-border); border-radius: 18px; }
|
||||
@@ -287,7 +302,7 @@ body {
|
||||
.avatar-component-card__label { font-size: 12px; font-weight: 700; color: var(--color-text); text-align: center; }
|
||||
.avatar-component-card:has(input:checked) { border-color: var(--color-primary); box-shadow: 0 0 0 3px rgba(52,72,90,0.14); }
|
||||
@media (max-width: 980px){ .avatar-component-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
|
||||
@media (max-width: 860px){ .avatar-builder__hero { grid-template-columns: 1fr; } .avatar-builder__actions { justify-content: center; } .avatar-component-colors { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); } .avatar-component-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||||
@media (max-width: 860px){ .avatar-builder__hero { grid-template-columns: 1fr; } .avatar-builder__actions { justify-content: center; } .avatar-component-colors { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); } .avatar-component-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } .profile-summary-grid { grid-template-columns: 1fr; } }
|
||||
.avatar-swatch-group { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; }
|
||||
.avatar-swatch { display: grid; grid-template-columns: 20px 18px minmax(0, 1fr); gap: 10px; align-items: center; padding: 10px 12px; border: 1px solid var(--color-border); border-radius: 14px; background: #fff; cursor: pointer; }
|
||||
.avatar-swatch__color { width: 18px; height: 18px; border-radius: 999px; background: var(--swatch-color); border: 1px solid rgba(0,0,0,0.12); }
|
||||
@@ -393,6 +408,9 @@ body {
|
||||
.cookie-consent__inner { grid-template-columns: 1fr; }
|
||||
.cookie-consent__actions { justify-content: stretch; }
|
||||
.account-kv div { grid-template-columns: 1fr; gap: 4px; }
|
||||
.account-kv--compact div { grid-template-columns: 1fr; gap: 4px; }
|
||||
.profile-edit-section summary,
|
||||
.profile-edit-section__body { padding-left: 14px; padding-right: 14px; }
|
||||
}
|
||||
|
||||
/* Auth & Dashboard */
|
||||
|
||||
@@ -182,10 +182,14 @@ final class AccountPages
|
||||
try {
|
||||
if ($action === 'profile') {
|
||||
$crypto = self::requireCrypto($crypto, 'Profil');
|
||||
$email = trim((string)($_POST['email'] ?? ''));
|
||||
$languages = $_POST['languages'] ?? '';
|
||||
if (is_array($languages)) {
|
||||
$languages = implode(', ', array_map('trim', $languages));
|
||||
}
|
||||
if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
throw new \RuntimeException('Bitte gib eine gueltige E-Mail-Adresse an.');
|
||||
}
|
||||
$firstNameEnc = self::encryptOptionalProfileField($crypto, trim((string)$_POST['first_name']));
|
||||
$lastNameEnc = self::encryptOptionalProfileField($crypto, trim((string)$_POST['last_name']));
|
||||
$streetEnc = self::encryptOptionalProfileField($crypto, trim((string)($_POST['street'] ?? '')));
|
||||
@@ -249,6 +253,11 @@ final class AccountPages
|
||||
'locationPref' => in_array($locationPreference, ['disabled', 'prompt', 'enabled'], true) ? $locationPreference : 'prompt',
|
||||
'id' => $userId,
|
||||
]);
|
||||
if ($pdo) {
|
||||
$emailStore = new UserEmailStore($pdo);
|
||||
$emailStore->ensureSchema();
|
||||
$emailStore->updateUserEmail($userId, $email);
|
||||
}
|
||||
$info = 'Profil gespeichert.';
|
||||
} elseif ($action === 'avatar_update') {
|
||||
if ($profileSettings) {
|
||||
|
||||
Reference in New Issue
Block a user