Bugfixes, Dating angefangen
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled

This commit is contained in:
2026-04-01 22:06:46 +02:00
parent 912718fc40
commit 87c85b1b17
123 changed files with 28977 additions and 462 deletions

View File

@@ -472,6 +472,55 @@
</div>
</div>
<!-- Dating -->
<div class="settings-section" id="sec-dating">
<div class="settings-section-header" onclick="toggleSection('sec-dating')">
<span class="settings-section-title">♥ Dating</span>
<span class="settings-section-arrow"></span>
</div>
<div class="settings-section-body">
<div class="settings-row">
<div class="settings-row-info">
<div class="settings-row-label">Dating aktivieren</div>
<div class="settings-row-desc">Zeige dein Profil im Dating-Bereich. Ein Standort ist erforderlich.</div>
</div>
<label style="display:flex;align-items:center;gap:0.5rem;cursor:pointer;">
<input type="checkbox" id="datingAktiv" style="width:1.1rem;height:1.1rem;accent-color:var(--color-primary);cursor:pointer;" onchange="onDatingToggle()">
</label>
</div>
<div id="datingStadtRow" style="display:none;">
<div class="settings-row" style="flex-wrap:wrap;gap:0.5rem;">
<div class="settings-row-info">
<div class="settings-row-label">Standort (Stadt)</div>
<div class="settings-row-desc">Wird im Dating-Bereich angezeigt.</div>
</div>
</div>
<div style="display:flex;gap:0.5rem;margin-bottom:0.35rem;position:relative;">
<div style="position:relative;flex:1;display:flex;">
<input type="text" id="datingStadt" placeholder="Stadt suchen und auswählen…" autocomplete="off"
style="flex:1;padding:0.55rem 2rem 0.55rem 0.8rem;border:1px solid var(--color-secondary);border-radius:6px;background:var(--color-secondary);color:var(--color-text);font-size:0.95rem;outline:none;"
oninput="onStadtInput()">
<button id="datingStadtClear" onclick="clearStadt()" title="Auswahl aufheben"
style="display:none;position:absolute;right:0.4rem;top:50%;transform:translateY(-50%);
background:none;border:none;color:var(--color-muted);font-size:1.1rem;cursor:pointer;padding:0.1rem 0.3rem;margin:0;line-height:1;">×</button>
</div>
<button onclick="detectLocation()" title="Standort ermitteln"
style="white-space:nowrap;padding:0.55rem 0.9rem;margin:0;font-size:0.85rem;">
⌖ Ermitteln
</button>
<ul id="stadtSuggestions" style="display:none;position:absolute;top:100%;left:0;right:3.5rem;
background:var(--color-card);border:1px solid var(--color-secondary);border-radius:6px;
z-index:100;list-style:none;margin:0.2rem 0 0;padding:0;max-height:200px;overflow-y:auto;"></ul>
</div>
<div id="datingLocMsg" style="font-size:0.82rem;color:var(--color-muted);margin-bottom:0.5rem;"></div>
</div>
<div class="settings-row" style="border-top:1px solid var(--color-secondary);padding-top:0.75rem;margin-top:0.25rem;">
<button onclick="saveDating()" id="saveDatingBtn" style="margin:0;padding:0.55rem 1.25rem;font-size:0.9rem;">Speichern</button>
<span id="datingMsg" style="font-size:0.85rem;"></span>
</div>
</div>
</div>
<!-- Datenschutz -->
<div class="settings-section" id="sec-datenschutz">
<div class="settings-section-header" onclick="toggleSection('sec-datenschutz')">
@@ -1134,8 +1183,152 @@
loadBdsmDefaults();
loadSubscription();
loadTtlockUserConfig();
loadDating();
openSectionFromHash();
// ── Dating ──────────────────────────────────────────────────────────────
async function loadDating() {
const res = await fetch('/login/me');
if (!res.ok) return;
const user = await res.json();
document.getElementById('datingAktiv').checked = !!user.datingAktiv;
if (user.datingStadt) {
const input = document.getElementById('datingStadt');
input.value = user.datingStadt;
input.readOnly = true;
document.getElementById('datingStadtClear').style.display = '';
}
if (user.datingLat != null) _datingLat = user.datingLat;
if (user.datingLon != null) _datingLon = user.datingLon;
document.getElementById('datingStadtRow').style.display = user.datingAktiv ? '' : 'none';
}
function onDatingToggle() {
const aktiv = document.getElementById('datingAktiv').checked;
document.getElementById('datingStadtRow').style.display = aktiv ? '' : 'none';
}
let _stadtSuggestTimer = null;
let _datingLat = null;
let _datingLon = null;
function onStadtInput() {
const q = document.getElementById('datingStadt').value.trim();
_datingLat = null;
_datingLon = null;
document.getElementById('datingStadtClear').style.display = 'none';
clearTimeout(_stadtSuggestTimer);
if (q.length < 2) { hideSuggestions(); return; }
_stadtSuggestTimer = setTimeout(() => fetchStadtSuggestions(q), 300);
}
async function fetchStadtSuggestions(q) {
try {
const url = `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(q)}&format=json&addressdetails=1&limit=5&featuretype=city`;
const res = await fetch(url, { headers: { 'Accept-Language': 'de' } });
if (!res.ok) return;
const results = await res.json();
const ul = document.getElementById('stadtSuggestions');
if (!results.length) { hideSuggestions(); return; }
ul.innerHTML = results.map(r => {
const city = r.address.city || r.address.town || r.address.village || r.address.county || r.name;
const country = r.address.country || '';
const label = city + (country ? ', ' + country : '');
return `<li style="padding:0.5rem 0.75rem;cursor:pointer;font-size:0.9rem;border-bottom:1px solid var(--color-secondary);"
onmousedown="selectStadt(event, '${label.replace(/'/g, "\\'")}', ${parseFloat(r.lat)}, ${parseFloat(r.lon)})">${label}</li>`;
}).join('');
ul.style.display = '';
} catch (_) { hideSuggestions(); }
}
function selectStadt(e, label, lat, lon) {
e.preventDefault();
const input = document.getElementById('datingStadt');
input.value = label;
input.readOnly = true;
_datingLat = lat;
_datingLon = lon;
document.getElementById('datingStadtClear').style.display = '';
hideSuggestions();
}
function clearStadt() {
const input = document.getElementById('datingStadt');
input.value = '';
input.readOnly = false;
_datingLat = null;
_datingLon = null;
document.getElementById('datingStadtClear').style.display = 'none';
input.focus();
}
function hideSuggestions() {
document.getElementById('stadtSuggestions').style.display = 'none';
}
document.addEventListener('click', e => {
if (!e.target.closest('#datingStadtRow')) hideSuggestions();
});
async function detectLocation() {
const msgEl = document.getElementById('datingLocMsg');
if (!navigator.geolocation) { msgEl.textContent = 'Geolocation nicht unterstützt.'; return; }
msgEl.textContent = 'Standort wird ermittelt…';
navigator.geolocation.getCurrentPosition(async pos => {
try {
const { latitude, longitude } = pos.coords;
const res = await fetch(
`https://nominatim.openstreetmap.org/reverse?lat=${latitude}&lon=${longitude}&format=json&addressdetails=1`,
{ headers: { 'Accept-Language': 'de' } }
);
if (!res.ok) throw new Error();
const data = await res.json();
const city = data.address.city || data.address.town || data.address.village || data.address.county || '';
const country = data.address.country || '';
const input = document.getElementById('datingStadt');
input.value = city + (country ? ', ' + country : '');
input.readOnly = true;
_datingLat = latitude;
_datingLon = longitude;
document.getElementById('datingStadtClear').style.display = '';
msgEl.textContent = '';
} catch (_) { msgEl.textContent = 'Standort konnte nicht ermittelt werden.'; }
}, () => { msgEl.textContent = 'Zugriff auf Standort verweigert.'; });
}
async function saveDating() {
const aktiv = document.getElementById('datingAktiv').checked;
const stadt = document.getElementById('datingStadt').value.trim();
const msgEl = document.getElementById('datingMsg');
if (aktiv && (!stadt || _datingLat == null || _datingLon == null)) {
msgEl.textContent = 'Bitte eine Stadt aus der Liste auswählen oder per GPS ermitteln.';
msgEl.style.color = 'var(--color-primary)';
return;
}
const btn = document.getElementById('saveDatingBtn');
btn.disabled = true;
try {
const res = await fetch('/user/me/dating', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ datingAktiv: aktiv, datingStadt: stadt || null, datingLat: _datingLat, datingLon: _datingLon })
});
if (res.ok) {
showToast();
msgEl.textContent = '';
} else {
msgEl.textContent = 'Fehler beim Speichern.';
msgEl.style.color = 'var(--color-primary)';
}
} catch (_) {
msgEl.textContent = 'Server nicht erreichbar.';
msgEl.style.color = 'var(--color-primary)';
} finally {
btn.disabled = false;
}
}
// ── TTLock ──────────────────────────────────────────────────────────────
let _ttlPasswordSet = false;