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.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- Ä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.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- 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
|
- 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
|
- 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
|
- 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
|
- Debug-Floating-Button nur für `site_admin` im Debug-Modus
|
||||||
|
|
||||||
## Technik
|
## Technik
|
||||||
|
|||||||
@@ -78,16 +78,31 @@ $sectionLinks = [
|
|||||||
<button class="btn" type="button" data-modal-open="modalAvatar">Profilbild gestalten</button>
|
<button class="btn" type="button" data-modal-open="modalAvatar">Profilbild gestalten</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<dl class="account-kv">
|
<div class="profile-summary-grid">
|
||||||
<div><dt>Name</dt><dd><?= htmlspecialchars(trim($profile['first_name'] . ' ' . $profile['last_name']), ENT_QUOTES) ?></dd></div>
|
<section class="profile-summary-card">
|
||||||
<div><dt>Anzeigename</dt><dd><?= htmlspecialchars($profile['display_name'], ENT_QUOTES) ?></dd></div>
|
<span class="profile-summary-card__eyebrow">Basis</span>
|
||||||
<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>
|
<dl class="account-kv account-kv--compact">
|
||||||
<div><dt>E-Mail</dt><dd><?= htmlspecialchars($profile['email'], ENT_QUOTES) ?></dd></div>
|
<div><dt>Anzeigename</dt><dd><?= htmlspecialchars($profile['display_name'] ?: 'Nicht hinterlegt', ENT_QUOTES) ?></dd></div>
|
||||||
<div><dt>Telefon</dt><dd><?= htmlspecialchars($profile['contact_phone'], 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>Beruf</dt><dd><?= htmlspecialchars($profile['profession'], ENT_QUOTES) ?></dd></div>
|
<div><dt>E-Mail</dt><dd><?= htmlspecialchars($profile['email'] ?: 'Nicht hinterlegt', ENT_QUOTES) ?></dd></div>
|
||||||
<div><dt>Sprachen</dt><dd><?= htmlspecialchars($profile['languages'], ENT_QUOTES) ?></dd></div>
|
<div><dt>Telefon</dt><dd><?= htmlspecialchars($profile['contact_phone'] ?: 'Nicht hinterlegt', ENT_QUOTES) ?></dd></div>
|
||||||
<div><dt>Über dich</dt><dd><?= htmlspecialchars($profile['about'], ENT_QUOTES) ?></dd></div>
|
|
||||||
</dl>
|
</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;">
|
<div class="flex gap-12" style="margin-top:18px;">
|
||||||
<button class="btn" type="button" data-modal-open="modalProfile">Profil bearbeiten</button>
|
<button class="btn" type="button" data-modal-open="modalProfile">Profil bearbeiten</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -484,49 +499,62 @@ $sectionLinks = [
|
|||||||
<h3>Profil bearbeiten</h3>
|
<h3>Profil bearbeiten</h3>
|
||||||
<button class="btn ghost" type="button" data-modal-close>✕</button>
|
<button class="btn ghost" type="button" data-modal-close>✕</button>
|
||||||
</div>
|
</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" name="action" value="profile">
|
||||||
<input type="hidden" id="pRegion" name="region" value="<?= htmlspecialchars((string)($profile['region'] ?? ''), ENT_QUOTES) ?>">
|
<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="pLat" name="lat" value="<?= htmlspecialchars((string)($profile['lat'] ?? ''), ENT_QUOTES) ?>">
|
||||||
<input type="hidden" id="pLng" name="lng" value="<?= htmlspecialchars((string)($profile['lng'] ?? ''), ENT_QUOTES) ?>">
|
<input type="hidden" id="pLng" name="lng" value="<?= htmlspecialchars((string)($profile['lng'] ?? ''), 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="form-grid">
|
||||||
<div class="stack gap-6">
|
<div class="stack gap-6">
|
||||||
<label class="label" for="pName">Anzeigename</label>
|
<label class="label" for="pName">Anzeigename</label>
|
||||||
<input id="pName" name="display_name" class="input" value="<?= htmlspecialchars($profile['display_name'], ENT_QUOTES) ?>">
|
<input id="pName" name="display_name" class="input" value="<?= htmlspecialchars($profile['display_name'], ENT_QUOTES) ?>">
|
||||||
</div>
|
</div>
|
||||||
<div class="stack gap-6">
|
<div class="stack gap-6">
|
||||||
<label class="label" for="pFirst">Vorname</label>
|
<label class="label" for="pEmail">E-Mail-Adresse</label>
|
||||||
<input id="pFirst" name="first_name" class="input" value="<?= htmlspecialchars($profile['first_name'], ENT_QUOTES) ?>">
|
<input id="pEmail" name="email" type="email" class="input" value="<?= htmlspecialchars((string)$profile['email'], ENT_QUOTES) ?>" autocomplete="email" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="stack gap-6">
|
<div class="stack gap-6">
|
||||||
<label class="label" for="pStreet">Straße / Hausnummer</label>
|
<label class="label" for="pFirst">Vorname</label>
|
||||||
<input id="pStreet" name="street" class="input" value="<?= htmlspecialchars((string)($profile['street'] ?? ''), ENT_QUOTES) ?>" placeholder="z. B. Musterstraße 12">
|
<input id="pFirst" name="first_name" class="input" value="<?= htmlspecialchars($profile['first_name'], ENT_QUOTES) ?>" autocomplete="given-name">
|
||||||
</div>
|
</div>
|
||||||
<div class="stack gap-6">
|
<div class="stack gap-6">
|
||||||
<label class="label" for="pLast">Nachname</label>
|
<label class="label" for="pLast">Nachname</label>
|
||||||
<input id="pLast" name="last_name" class="input" value="<?= htmlspecialchars($profile['last_name'], ENT_QUOTES) ?>">
|
<input id="pLast" name="last_name" class="input" value="<?= htmlspecialchars($profile['last_name'], ENT_QUOTES) ?>" autocomplete="family-name">
|
||||||
</div>
|
</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">
|
<div class="stack gap-6">
|
||||||
<label class="label" for="pPhone">Telefon (mobil)</label>
|
<label class="label" for="pPhone">Telefon (mobil)</label>
|
||||||
<input id="pPhone" name="contact_phone" class="input" value="<?= htmlspecialchars($profile['contact_phone'], ENT_QUOTES) ?>">
|
<input id="pPhone" name="contact_phone" class="input" value="<?= htmlspecialchars($profile['contact_phone'], ENT_QUOTES) ?>" autocomplete="tel">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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>
|
</div>
|
||||||
<div class="flex gap-12" style="flex-wrap:wrap;">
|
<div class="flex gap-12" style="flex-wrap:wrap;">
|
||||||
@@ -544,6 +572,11 @@ $sectionLinks = [
|
|||||||
<div id="profileAddressNeedsValidation" class="muted small" style="margin:0; display:none;">
|
<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.
|
Die Adresse wurde manuell geaendert. Vor dem Speichern bitte erneut suchen oder die Browser-Adresse uebernehmen.
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
<details class="profile-edit-section">
|
||||||
|
<summary>Sprache und Beruf</summary>
|
||||||
|
<div class="profile-edit-section__body">
|
||||||
<div class="stack gap-6">
|
<div class="stack gap-6">
|
||||||
<label class="label">Sprachen (Mehrfachauswahl)</label>
|
<label class="label">Sprachen (Mehrfachauswahl)</label>
|
||||||
<div class="chips" style="flex-wrap: wrap;">
|
<div class="chips" style="flex-wrap: wrap;">
|
||||||
@@ -563,12 +596,19 @@ $sectionLinks = [
|
|||||||
</div>
|
</div>
|
||||||
<div class="stack gap-6">
|
<div class="stack gap-6">
|
||||||
<label class="label" for="pProf">Beruf</label>
|
<label class="label" for="pProf">Beruf</label>
|
||||||
<input id="pProf" name="profession" class="input" value="<?= htmlspecialchars($profile['profession'], ENT_QUOTES) ?>">
|
<input id="pProf" name="profession" class="input" value="<?= htmlspecialchars($profile['profession'], ENT_QUOTES) ?>" autocomplete="organization-title">
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
<details class="profile-edit-section">
|
||||||
|
<summary>Kurzvorstellung</summary>
|
||||||
|
<div class="profile-edit-section__body">
|
||||||
<div class="stack gap-6">
|
<div class="stack gap-6">
|
||||||
<label class="label" for="pAbout">Kurzvorstellung</label>
|
<label class="label" for="pAbout">Über dich</label>
|
||||||
<textarea id="pAbout" name="about" class="textarea" rows="3"><?= htmlspecialchars($profile['about'], ENT_QUOTES) ?></textarea>
|
<textarea id="pAbout" name="about" class="textarea" rows="4"><?= htmlspecialchars($profile['about'], ENT_QUOTES) ?></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
<div class="flex gap-12">
|
<div class="flex gap-12">
|
||||||
<button class="btn ghost" type="button" data-modal-close>Abbrechen</button>
|
<button class="btn ghost" type="button" data-modal-close>Abbrechen</button>
|
||||||
<button class="btn" type="submit">Speichern</button>
|
<button class="btn" type="submit">Speichern</button>
|
||||||
|
|||||||
@@ -241,12 +241,27 @@ body {
|
|||||||
.account-panel__body { padding: 24px; }
|
.account-panel__body { padding: 24px; }
|
||||||
.account-kv { display: grid; gap: 10px; margin: 0; }
|
.account-kv { display: grid; gap: 10px; margin: 0; }
|
||||||
.account-kv div { display: grid; grid-template-columns: 190px minmax(0, 1fr); gap: 14px; }
|
.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 dt { font-weight: 700; color: var(--color-muted); }
|
||||||
.account-kv dd { margin: 0; }
|
.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 { 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__preview { display: flex; justify-content: center; }
|
||||||
.profile-avatar-panel__content h3 { margin: 0 0 8px; }
|
.profile-avatar-panel__content h3 { margin: 0 0 8px; }
|
||||||
.profile-avatar-panel__content p { margin: 0 0 14px; }
|
.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 { 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__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; }
|
.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__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); }
|
.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: 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-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 { 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); }
|
.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__inner { grid-template-columns: 1fr; }
|
||||||
.cookie-consent__actions { justify-content: stretch; }
|
.cookie-consent__actions { justify-content: stretch; }
|
||||||
.account-kv div { grid-template-columns: 1fr; gap: 4px; }
|
.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 */
|
/* Auth & Dashboard */
|
||||||
|
|||||||
@@ -182,10 +182,14 @@ final class AccountPages
|
|||||||
try {
|
try {
|
||||||
if ($action === 'profile') {
|
if ($action === 'profile') {
|
||||||
$crypto = self::requireCrypto($crypto, 'Profil');
|
$crypto = self::requireCrypto($crypto, 'Profil');
|
||||||
|
$email = trim((string)($_POST['email'] ?? ''));
|
||||||
$languages = $_POST['languages'] ?? '';
|
$languages = $_POST['languages'] ?? '';
|
||||||
if (is_array($languages)) {
|
if (is_array($languages)) {
|
||||||
$languages = implode(', ', array_map('trim', $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']));
|
$firstNameEnc = self::encryptOptionalProfileField($crypto, trim((string)$_POST['first_name']));
|
||||||
$lastNameEnc = self::encryptOptionalProfileField($crypto, trim((string)$_POST['last_name']));
|
$lastNameEnc = self::encryptOptionalProfileField($crypto, trim((string)$_POST['last_name']));
|
||||||
$streetEnc = self::encryptOptionalProfileField($crypto, trim((string)($_POST['street'] ?? '')));
|
$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',
|
'locationPref' => in_array($locationPreference, ['disabled', 'prompt', 'enabled'], true) ? $locationPreference : 'prompt',
|
||||||
'id' => $userId,
|
'id' => $userId,
|
||||||
]);
|
]);
|
||||||
|
if ($pdo) {
|
||||||
|
$emailStore = new UserEmailStore($pdo);
|
||||||
|
$emailStore->ensureSchema();
|
||||||
|
$emailStore->updateUserEmail($userId, $email);
|
||||||
|
}
|
||||||
$info = 'Profil gespeichert.';
|
$info = 'Profil gespeichert.';
|
||||||
} elseif ($action === 'avatar_update') {
|
} elseif ($action === 'avatar_update') {
|
||||||
if ($profileSettings) {
|
if ($profileSettings) {
|
||||||
|
|||||||
Reference in New Issue
Block a user