767 lines
30 KiB
HTML
767 lines
30 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<link rel="icon" href="/img/icon.png" type="image/png">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Profil – xXx Sphere</title>
|
||
<link rel="stylesheet" href="/css/variables.css">
|
||
<link rel="stylesheet" href="/css/style.css">
|
||
<style>
|
||
.profile-picture-wrap {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 1rem;
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
.profile-picture {
|
||
width: 120px;
|
||
height: 120px;
|
||
border-radius: 50%;
|
||
object-fit: cover;
|
||
border: 3px solid var(--color-secondary);
|
||
background: var(--color-secondary);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 3rem;
|
||
color: var(--color-muted);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.profile-picture img {
|
||
width: 120px;
|
||
height: 120px;
|
||
border-radius: 50%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.profile-field {
|
||
margin-bottom: 1.25rem;
|
||
}
|
||
|
||
.profile-field label {
|
||
margin-top: 0;
|
||
}
|
||
|
||
.profile-field-row {
|
||
display: flex;
|
||
align-items: stretch;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.profile-field-row p {
|
||
flex: 1;
|
||
padding: 0.65rem 0.9rem;
|
||
border: 1px solid var(--color-secondary);
|
||
border-radius: 6px;
|
||
background: var(--color-secondary);
|
||
font-size: 1rem;
|
||
color: var(--color-text);
|
||
margin: 0;
|
||
}
|
||
|
||
.profile-field-row button {
|
||
flex-shrink: 0;
|
||
width: 175px;
|
||
padding: 0.65rem 0.75rem;
|
||
margin-top: 0;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
input[type="file"] {
|
||
font-size: 0.85rem;
|
||
color: var(--color-muted);
|
||
}
|
||
|
||
/* ── Gallery ── */
|
||
.gallery-section-label {
|
||
font-size: 0.8rem;
|
||
font-weight: 600;
|
||
color: var(--color-muted);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.05em;
|
||
margin: 1.75rem 0 0.75rem;
|
||
border-top: 1px solid var(--color-secondary);
|
||
padding-top: 1.5rem;
|
||
}
|
||
|
||
.gallery-upload-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
/* ── Vorlieben Tabs ── */
|
||
.vl-tabs { display: flex; gap: 0; flex-wrap: wrap;
|
||
border-bottom: 1px solid var(--color-secondary); margin-bottom: 1rem; }
|
||
.vl-tab-btn { background: none; border: none; border-bottom: 3px solid transparent;
|
||
border-radius: 0; padding: 0.5rem 1rem; font-size: 0.85rem; font-weight: 600;
|
||
color: var(--color-muted); cursor: pointer; margin-bottom: -1px;
|
||
transition: color .15s, border-color .15s; white-space: nowrap; }
|
||
.vl-tab-btn:hover { color: var(--color-text); background: none; }
|
||
.vl-tab-btn.active { color: var(--color-primary); border-bottom-color: var(--color-primary); }
|
||
.vl-tab-panel { display: none; }
|
||
.vl-tab-panel.active { display: block; }
|
||
|
||
/* ── Vorlieben Items ── */
|
||
.vorliebe-row {
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
gap: 0.75rem; padding: 0.55rem 0.75rem; border-radius: 8px;
|
||
background: var(--color-card); border: 1px solid var(--color-secondary);
|
||
margin-bottom: 0.45rem;
|
||
}
|
||
.vorliebe-row-name { font-size: 0.9rem; flex: 1; min-width: 0; }
|
||
.vorliebe-smileys { display: flex; gap: 0.25rem; flex-shrink: 0; }
|
||
.vl-smiley {
|
||
display: inline-flex; align-items: center; cursor: pointer;
|
||
border: 2px solid transparent; border-radius: 6px;
|
||
padding: 0.1rem 0.15rem;
|
||
transition: border-color .15s, transform .1s;
|
||
user-select: none;
|
||
}
|
||
.vl-smiley img { height: 1.6rem; width: auto; display: block; }
|
||
.vl-smiley:hover { transform: scale(1.15); }
|
||
.vl-smiley.active { border-color: var(--vl-color); }
|
||
.vorlieben-hint { font-size: 0.82rem; color: var(--color-muted); margin-bottom: 1rem; }
|
||
|
||
#ownGallery {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 0.4rem;
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.own-thumb {
|
||
aspect-ratio: 1;
|
||
overflow: hidden;
|
||
border-radius: 6px;
|
||
position: relative;
|
||
background: var(--color-secondary);
|
||
cursor: pointer;
|
||
}
|
||
|
||
.own-thumb img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
display: block;
|
||
}
|
||
|
||
.own-thumb-delete {
|
||
position: absolute;
|
||
top: 4px;
|
||
right: 4px;
|
||
background: rgba(0,0,0,0.6);
|
||
border: none;
|
||
color: #fff;
|
||
border-radius: 50%;
|
||
width: 1.5rem;
|
||
height: 1.5rem;
|
||
font-size: 0.75rem;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 0;
|
||
margin: 0;
|
||
transition: background 0.15s;
|
||
}
|
||
|
||
.own-thumb:hover .own-thumb-delete { display: flex; }
|
||
.own-thumb-delete:hover { background: rgba(180,30,30,0.85); }
|
||
|
||
.btn-delete-row {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
margin-bottom: 1.25rem;
|
||
}
|
||
|
||
.btn-delete {
|
||
width: 175px;
|
||
padding: 0.65rem 0.75rem;
|
||
margin-top: 0;
|
||
background: transparent;
|
||
border: 1px solid var(--color-secondary);
|
||
border-radius: 6px;
|
||
color: var(--color-muted);
|
||
font-size: 0.85rem;
|
||
font-weight: normal;
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
transition: background 0.15s, border-color 0.15s, color 0.15s;
|
||
}
|
||
|
||
.btn-delete:hover {
|
||
background: #3d0f1a;
|
||
border-color: var(--color-primary);
|
||
color: var(--color-primary);
|
||
}
|
||
|
||
/* ── Profile extras ── */
|
||
.profile-extras-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 0.75rem 1rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.profile-extras-grid .full-col {
|
||
grid-column: 1 / -1;
|
||
}
|
||
|
||
select {
|
||
width: 100%;
|
||
padding: 0.65rem 0.9rem;
|
||
border: 1px solid var(--color-secondary);
|
||
border-radius: 6px;
|
||
background: var(--color-secondary);
|
||
color: var(--color-text);
|
||
font-size: 1rem;
|
||
outline: none;
|
||
transition: border-color 0.2s;
|
||
appearance: none;
|
||
-webkit-appearance: none;
|
||
}
|
||
|
||
select:focus { border-color: var(--color-primary); }
|
||
|
||
textarea {
|
||
width: 100%;
|
||
padding: 0.65rem 0.9rem;
|
||
border: 1px solid var(--color-secondary);
|
||
border-radius: 6px;
|
||
background: var(--color-secondary);
|
||
color: var(--color-text);
|
||
font-size: 1rem;
|
||
outline: none;
|
||
resize: vertical;
|
||
min-height: 100px;
|
||
transition: border-color 0.2s;
|
||
font-family: inherit;
|
||
}
|
||
|
||
textarea:focus { border-color: var(--color-primary); }
|
||
|
||
.char-count {
|
||
font-size: 0.75rem;
|
||
color: var(--color-muted);
|
||
text-align: right;
|
||
margin-top: 0.25rem;
|
||
}
|
||
|
||
.char-count.over { color: var(--color-primary); }
|
||
|
||
/* ── Modal ── */
|
||
.modal-backdrop {
|
||
display: none;
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
z-index: 200;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.modal-backdrop.visible {
|
||
display: flex;
|
||
}
|
||
|
||
.modal {
|
||
background: var(--color-card);
|
||
border: 1px solid var(--color-secondary);
|
||
border-radius: 12px;
|
||
padding: 2rem;
|
||
width: 100%;
|
||
max-width: 360px;
|
||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
|
||
}
|
||
|
||
.modal h2 {
|
||
color: var(--color-primary);
|
||
font-size: 1.2rem;
|
||
margin-bottom: 1.25rem;
|
||
}
|
||
|
||
.modal-actions {
|
||
display: flex;
|
||
gap: 0.75rem;
|
||
margin-top: 1.25rem;
|
||
}
|
||
|
||
.modal-actions button {
|
||
flex: 1;
|
||
margin-top: 0;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body class="app">
|
||
|
||
<div class="main">
|
||
<div class="content">
|
||
|
||
<div class="profile-picture-wrap">
|
||
<div class="profile-picture" id="profilePicDisplay">◉</div>
|
||
<input type="file" id="picFile" accept="image/*">
|
||
</div>
|
||
|
||
<!-- Freiwillige Profilangaben -->
|
||
<div class="gallery-section-label" style="margin-top:1.25rem;">Freiwillige Angaben</div>
|
||
<div class="profile-extras-grid">
|
||
<div>
|
||
<label>Alter</label>
|
||
<input type="text" id="profileAlter" readonly style="background:transparent;cursor:default;color:var(--color-muted);" placeholder="—">
|
||
</div>
|
||
<div>
|
||
<label>Größe (cm)</label>
|
||
<input type="number" id="profileGroesse" min="100" max="250" placeholder="—">
|
||
</div>
|
||
<div>
|
||
<label>Gewicht (kg)</label>
|
||
<input type="number" id="profileGewicht" min="30" max="300" placeholder="—">
|
||
</div>
|
||
<div>
|
||
<label>Geschlecht</label>
|
||
<select id="profileGeschlecht">
|
||
<option value="">— keine Angabe —</option>
|
||
<option value="WEIBLICH">weiblich</option>
|
||
<option value="DIVERS">divers</option>
|
||
<option value="MAENNLICH">männlich</option>
|
||
</select>
|
||
</div>
|
||
<div class="full-col">
|
||
<label>Neigung</label>
|
||
<select id="profileNeigung">
|
||
<option value="">— keine Angabe —</option>
|
||
<option value="DEVOT">devot</option>
|
||
<option value="EHER_DEVOT">eher devot</option>
|
||
<option value="SWITCHER">Switcher</option>
|
||
<option value="EHER_DOMINANT">eher dominant</option>
|
||
<option value="DOMINANT">dominant</option>
|
||
<option value="KEINES">keines</option>
|
||
</select>
|
||
</div>
|
||
<div class="full-col">
|
||
<label>Beziehungsstatus</label>
|
||
<select id="profileBeziehungsstatus">
|
||
<option value="">— keine Angabe —</option>
|
||
<option value="SINGLE">single</option>
|
||
<option value="IN_EINER_BEZIEHUNG">in einer Beziehung</option>
|
||
<option value="VERHEIRATET">verheiratet</option>
|
||
<option value="IN_EINER_OFFENEN_BEZIEHUNG">in einer offenen Beziehung</option>
|
||
<option value="IN_EINER_OFFENEN_EHE">in einer offenen Ehe</option>
|
||
</select>
|
||
</div>
|
||
<div class="full-col">
|
||
<label>Über mich</label>
|
||
<textarea id="profileBeschreibung" maxlength="600" placeholder="Erzähl etwas über dich…" oninput="updateCharCount()"></textarea>
|
||
<div class="char-count" id="charCount">0 / 600</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="message" id="message"></div>
|
||
|
||
<button class="full-width" id="saveBtn" onclick="saveProfile()">Profil speichern</button>
|
||
|
||
<div class="gallery-section-label" style="margin-top:1.5rem;">Vorlieben</div>
|
||
<p class="vorlieben-hint">Wähle für jede Vorliebe aus, wie du dazu stehst. Nicht ausgefüllte Einträge werden nicht angezeigt.</p>
|
||
<div id="vorliebenSection"><p style="color:var(--color-muted);font-size:0.85rem;">Wird geladen…</p></div>
|
||
<div class="message" id="vorliebenMessage" style="margin-top:0.75rem;display:none;"></div>
|
||
<button class="full-width" id="saveVorliebenBtn" onclick="saveVorlieben()" style="margin-bottom:1.5rem;">Vorlieben speichern</button>
|
||
|
||
<div class="gallery-section-label">Meine Bilder</div>
|
||
<div class="gallery-upload-row">
|
||
<input type="file" id="galleryFile" accept="image/*" multiple style="display:none;" onchange="handleGalleryUpload(this.files)">
|
||
<button onclick="document.getElementById('galleryFile').click()">+ Bilder hochladen</button>
|
||
<span id="galleryUploadStatus" style="font-size:0.85rem;color:var(--color-muted);"></span>
|
||
</div>
|
||
<div id="ownGallery"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="/js/shared.js"></script>
|
||
<script src="/js/image-viewer.js"></script>
|
||
<script src="/js/icons.js"></script>
|
||
<script src="/js/sidebar.js"></script>
|
||
<script>
|
||
let currentPicture = null;
|
||
let currentPictureHq = null;
|
||
let pictureDirty = false;
|
||
let allGalleryImages = [];
|
||
|
||
fetch('/login/me')
|
||
.then(r => {
|
||
if (r.status === 401) { window.location.href = '/login.html'; return null; }
|
||
return r.json();
|
||
})
|
||
.then(user => {
|
||
if (!user) return;
|
||
if (user.profilePicture) {
|
||
currentPicture = user.profilePicture;
|
||
renderPicture(currentPicture);
|
||
}
|
||
// Fill optional profile fields
|
||
if (user.geburtsdatum) {
|
||
const birth = new Date(user.geburtsdatum);
|
||
const today = new Date();
|
||
const age = today.getFullYear() - birth.getFullYear()
|
||
- (today < new Date(today.getFullYear(), birth.getMonth(), birth.getDate()) ? 1 : 0);
|
||
document.getElementById('profileAlter').value = age + ' Jahre';
|
||
}
|
||
if (user.groesse) document.getElementById('profileGroesse').value = user.groesse;
|
||
if (user.gewicht) document.getElementById('profileGewicht').value = user.gewicht;
|
||
if (user.geschlecht) document.getElementById('profileGeschlecht').value = user.geschlecht;
|
||
if (user.neigung) document.getElementById('profileNeigung').value = user.neigung;
|
||
if (user.beziehungsstatus) document.getElementById('profileBeziehungsstatus').value = user.beziehungsstatus;
|
||
if (user.beschreibung) {
|
||
document.getElementById('profileBeschreibung').value = user.beschreibung;
|
||
updateCharCount();
|
||
}
|
||
myUserId = user.userId;
|
||
loadOwnGallery();
|
||
loadVorliebenEdit();
|
||
})
|
||
.catch(() => { window.location.href = '/login.html'; });
|
||
|
||
// Check if redirected back after email change
|
||
if (new URLSearchParams(window.location.search).get('emailChanged')) {
|
||
// Already handled by login.html redirect — nothing to do here
|
||
}
|
||
|
||
document.getElementById('picFile').addEventListener('change', async e => {
|
||
const file = e.target.files[0];
|
||
if (!file) return;
|
||
[currentPicture, currentPictureHq] = await Promise.all([toBase64(file, 96), toBase64(file, 1024)]);
|
||
pictureDirty = true;
|
||
renderPicture(currentPicture);
|
||
});
|
||
|
||
function renderPicture(base64) {
|
||
const el = document.getElementById('profilePicDisplay');
|
||
el.innerHTML = `<img src="data:image/png;base64,${base64}" alt="Profilbild">`;
|
||
}
|
||
|
||
function updateCharCount() {
|
||
const ta = document.getElementById('profileBeschreibung');
|
||
const el = document.getElementById('charCount');
|
||
const len = ta.value.length;
|
||
el.textContent = len + ' / 600';
|
||
el.classList.toggle('over', len > 600);
|
||
}
|
||
|
||
async function saveProfile() {
|
||
const btn = document.getElementById('saveBtn');
|
||
btn.disabled = true;
|
||
btn.textContent = 'Wird gespeichert…';
|
||
hideMessage();
|
||
|
||
const beschreibung = document.getElementById('profileBeschreibung').value.trim();
|
||
if (beschreibung.length > 600) {
|
||
showMessage('Die Beschreibung darf maximal 600 Zeichen lang sein.', 'error');
|
||
btn.disabled = false;
|
||
btn.textContent = 'Profil speichern';
|
||
return;
|
||
}
|
||
|
||
const toNullOrInt = id => {
|
||
const v = document.getElementById(id).value;
|
||
return v ? parseInt(v, 10) : null;
|
||
};
|
||
const toNullOrStr = id => {
|
||
const v = document.getElementById(id).value;
|
||
return v || null;
|
||
};
|
||
|
||
try {
|
||
const [picRes, profileRes] = await Promise.all([
|
||
pictureDirty ? fetch('/user/me/picture', {
|
||
method: 'PUT',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ picture: currentPicture, pictureHq: currentPictureHq })
|
||
}) : Promise.resolve({ ok: true }),
|
||
fetch('/user/me/profile', {
|
||
method: 'PUT',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
groesse: toNullOrInt('profileGroesse'),
|
||
gewicht: toNullOrInt('profileGewicht'),
|
||
geschlecht: toNullOrStr('profileGeschlecht'),
|
||
neigung: toNullOrStr('profileNeigung'),
|
||
beziehungsstatus: toNullOrStr('profileBeziehungsstatus'),
|
||
beschreibung: beschreibung || null
|
||
})
|
||
})
|
||
]);
|
||
if (picRes.ok && profileRes.ok) {
|
||
showMessage('Gespeichert!', 'success');
|
||
} else {
|
||
showMessage(`Fehler beim Speichern.`, 'error');
|
||
}
|
||
} catch (err) {
|
||
showMessage('Server nicht erreichbar.', 'error');
|
||
console.error(err);
|
||
} finally {
|
||
btn.disabled = false;
|
||
btn.textContent = 'Profil speichern';
|
||
}
|
||
}
|
||
|
||
// ── Gallery ──
|
||
let myUserId = null;
|
||
|
||
async function loadOwnGallery() {
|
||
if (!myUserId) return;
|
||
const res = await fetch('/social/profile-images?userId=' + myUserId);
|
||
if (!res.ok) return;
|
||
const images = await res.json();
|
||
renderOwnGallery(images);
|
||
}
|
||
|
||
function renderOwnGallery(images) {
|
||
allGalleryImages = images;
|
||
const grid = document.getElementById('ownGallery');
|
||
if (images.length === 0) {
|
||
grid.innerHTML = '<p style="color:var(--color-muted);font-size:0.85rem;grid-column:1/-1;">Noch keine Bilder hochgeladen.</p>';
|
||
return;
|
||
}
|
||
grid.innerHTML = images.map((img, i) => `
|
||
<div class="own-thumb" onclick="openGalleryViewer(${i})" style="cursor:pointer;">
|
||
<img src="data:image/jpeg;base64,${img.imageData}" alt="Galerie-Bild">
|
||
<button class="own-thumb-delete" onclick="deleteGalleryImage('${img.imageId}', event)" title="Bild löschen">✕</button>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function openGalleryViewer(index) {
|
||
imageViewer.open({
|
||
images: allGalleryImages.map(img => ({
|
||
src: 'data:image/jpeg;base64,' + img.imageData,
|
||
id: img.imageId,
|
||
likedByMe: false,
|
||
likeCount: 0
|
||
})),
|
||
index,
|
||
showLike: false,
|
||
showComments: false
|
||
});
|
||
}
|
||
|
||
async function deleteGalleryImage(imageId, event) {
|
||
event.stopPropagation();
|
||
const res = await fetch('/social/profile-images/' + imageId, { method: 'DELETE' });
|
||
if (res.ok || res.status === 204) loadOwnGallery();
|
||
}
|
||
|
||
async function handleGalleryUpload(files) {
|
||
if (!files || files.length === 0) return;
|
||
const status = document.getElementById('galleryUploadStatus');
|
||
status.textContent = '0 / ' + files.length + ' hochgeladen…';
|
||
let done = 0;
|
||
for (const file of Array.from(files)) {
|
||
try {
|
||
const base64 = await toJpeg(file, 1024);
|
||
const res = await fetch('/social/profile-images', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ imageData: base64 })
|
||
});
|
||
if (res.status === 422) {
|
||
status.textContent = 'Limit von 20 Bildern erreicht.';
|
||
break;
|
||
}
|
||
} catch (e) {
|
||
console.error('Upload-Fehler:', e);
|
||
}
|
||
done++;
|
||
status.textContent = done + ' / ' + files.length + ' hochgeladen…';
|
||
}
|
||
status.textContent = done + ' Bild' + (done !== 1 ? 'er' : '') + ' hochgeladen.';
|
||
document.getElementById('galleryFile').value = '';
|
||
loadOwnGallery();
|
||
}
|
||
|
||
function toJpeg(file, max) {
|
||
return new Promise((resolve, reject) => {
|
||
const img = new Image();
|
||
const url = URL.createObjectURL(file);
|
||
img.onload = () => {
|
||
URL.revokeObjectURL(url);
|
||
let w = img.naturalWidth, h = img.naturalHeight;
|
||
if (w > max || h > max) {
|
||
if (w >= h) { h = Math.max(1, Math.round(max * h / w)); w = max; }
|
||
else { w = Math.max(1, Math.round(max * w / h)); h = max; }
|
||
}
|
||
const canvas = document.createElement('canvas');
|
||
canvas.width = w; canvas.height = h;
|
||
canvas.getContext('2d').drawImage(img, 0, 0, w, h);
|
||
resolve(canvas.toDataURL('image/jpeg', 0.85).split(',')[1]);
|
||
};
|
||
img.onerror = reject;
|
||
img.src = url;
|
||
});
|
||
}
|
||
|
||
function toBase64(file, max) {
|
||
return new Promise((resolve, reject) => {
|
||
const img = new Image();
|
||
const url = URL.createObjectURL(file);
|
||
img.onload = () => {
|
||
URL.revokeObjectURL(url);
|
||
let w = img.naturalWidth, h = img.naturalHeight;
|
||
if (w > max || h > max) {
|
||
if (w >= h) { h = Math.max(1, Math.round(max * h / w)); w = max; }
|
||
else { w = Math.max(1, Math.round(max * w / h)); h = max; }
|
||
}
|
||
const canvas = document.createElement('canvas');
|
||
canvas.width = w; canvas.height = h;
|
||
canvas.getContext('2d').drawImage(img, 0, 0, w, h);
|
||
resolve(canvas.toDataURL('image/png').split(',')[1]);
|
||
};
|
||
img.onerror = reject;
|
||
img.src = url;
|
||
});
|
||
}
|
||
|
||
function showMessage(text, type) {
|
||
const el = document.getElementById('message');
|
||
el.textContent = text;
|
||
el.className = `message ${type}`;
|
||
el.style.display = 'block';
|
||
}
|
||
|
||
function hideMessage() {
|
||
document.getElementById('message').style.display = 'none';
|
||
}
|
||
|
||
// ── Vorlieben ──────────────────────────────────────────────────────────────
|
||
|
||
const VL_SMILEYS = [
|
||
{ value: 'GEHT_GAR_NICHT', img: '/img/vorlieben/verynegative.png', color: '#e53935', title: 'Geht gar nicht' },
|
||
{ value: 'EHER_NICHT', img: '/img/vorlieben/negative.png', color: '#fb8c00', title: 'Eher nicht' },
|
||
{ value: 'NEUTRAL', img: '/img/vorlieben/neutral.png', color: '#fdd835', title: 'Neutral' },
|
||
{ value: 'MAG_ICH', img: '/img/vorlieben/positiv.png', color: '#81c784', title: 'Mag ich' },
|
||
{ value: 'UNBEDINGT', img: '/img/vorlieben/verypositiv.png', color: '#2e7d32', title: 'Unbedingt' },
|
||
{ value: 'WILL_AUSPROBIEREN', img: '/img/vorlieben/dunno.png', color: '#1e88e5', title: 'Will ich ausprobieren' },
|
||
];
|
||
|
||
let vorliebenRatings = {};
|
||
|
||
async function loadVorliebenEdit() {
|
||
const container = document.getElementById('vorliebenSection');
|
||
try {
|
||
const [itemsRes, meRes] = await Promise.all([
|
||
fetch('/vorlieben/items'),
|
||
fetch('/vorlieben/me'),
|
||
]);
|
||
if (!itemsRes.ok) {
|
||
container.innerHTML = '<p style="color:var(--color-muted);font-size:0.85rem;">Keine Vorlieben konfiguriert.</p>';
|
||
return;
|
||
}
|
||
const kategorien = await itemsRes.json();
|
||
vorliebenRatings = meRes.ok ? await meRes.json() : {};
|
||
|
||
if (!kategorien.length) {
|
||
container.innerHTML = '<p style="color:var(--color-muted);font-size:0.85rem;">Keine Vorlieben konfiguriert.</p>';
|
||
return;
|
||
}
|
||
|
||
const smileysHtml = VL_SMILEYS.map(s =>
|
||
`<span class="vl-smiley" data-val="${s.value}" title="${s.title}" style="--vl-color:${s.color}"><img src="${s.img}" alt="${s.title}"></span>`
|
||
).join('');
|
||
|
||
// Tab buttons
|
||
const tabBtns = kategorien.map((kat, i) =>
|
||
`<button class="vl-tab-btn${i === 0 ? ' active' : ''}" onclick="switchVlTab('vlkat-${kat.kategorieId}', this)">${escapeHtml(kat.name)}</button>`
|
||
).join('');
|
||
|
||
// Tab panels
|
||
const tabPanels = kategorien.map((kat, i) => `
|
||
<div class="vl-tab-panel${i === 0 ? ' active' : ''}" id="vlkat-${kat.kategorieId}">
|
||
${kat.items.map(item => `
|
||
<div class="vorliebe-row">
|
||
<span class="vorliebe-row-name">${escapeHtml(item.name)}</span>
|
||
<div class="vorliebe-smileys" data-item-id="${item.itemId}">
|
||
${smileysHtml}
|
||
</div>
|
||
</div>`).join('')}
|
||
</div>`).join('');
|
||
|
||
container.innerHTML = `<div class="vl-tabs">${tabBtns}</div>${tabPanels}`;
|
||
|
||
// Mark saved values
|
||
container.querySelectorAll('.vorliebe-smileys[data-item-id]').forEach(group => {
|
||
const saved = vorliebenRatings[group.dataset.itemId];
|
||
if (saved) {
|
||
const btn = group.querySelector(`.vl-smiley[data-val="${saved}"]`);
|
||
if (btn) btn.classList.add('active');
|
||
}
|
||
});
|
||
|
||
// Click handler
|
||
container.addEventListener('click', e => {
|
||
const btn = e.target.closest('.vl-smiley');
|
||
if (!btn) return;
|
||
const group = btn.closest('.vorliebe-smileys');
|
||
const itemId = group.dataset.itemId;
|
||
const isActive = btn.classList.contains('active');
|
||
// Deselect all in this group
|
||
group.querySelectorAll('.vl-smiley').forEach(s => s.classList.remove('active'));
|
||
if (!isActive) {
|
||
btn.classList.add('active');
|
||
vorliebenRatings[itemId] = btn.dataset.val;
|
||
} else {
|
||
delete vorliebenRatings[itemId];
|
||
}
|
||
});
|
||
|
||
} catch (e) {
|
||
container.innerHTML = '<p style="color:var(--color-muted);font-size:0.85rem;">Fehler beim Laden.</p>';
|
||
}
|
||
}
|
||
|
||
function switchVlTab(panelId, btn) {
|
||
document.querySelectorAll('.vl-tab-btn').forEach(b => b.classList.remove('active'));
|
||
document.querySelectorAll('.vl-tab-panel').forEach(p => p.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
document.getElementById(panelId).classList.add('active');
|
||
}
|
||
|
||
async function saveVorlieben() {
|
||
const btn = document.getElementById('saveVorliebenBtn');
|
||
const msgEl = document.getElementById('vorliebenMessage');
|
||
btn.disabled = true; btn.textContent = 'Wird gespeichert…';
|
||
|
||
// Collect: all known items → current rating or null
|
||
const ratings = {};
|
||
document.querySelectorAll('#vorliebenSection .vorliebe-smileys[data-item-id]').forEach(group => {
|
||
const active = group.querySelector('.vl-smiley.active');
|
||
ratings[group.dataset.itemId] = active ? active.dataset.val : null;
|
||
});
|
||
|
||
try {
|
||
const res = await fetch('/vorlieben/me', {
|
||
method: 'PUT', headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(ratings),
|
||
});
|
||
msgEl.textContent = res.ok ? 'Vorlieben gespeichert.' : 'Fehler beim Speichern.';
|
||
msgEl.className = `message ${res.ok ? 'success' : 'error'}`;
|
||
msgEl.style.display = 'block';
|
||
setTimeout(() => { msgEl.style.display = 'none'; }, 3000);
|
||
} catch (e) {
|
||
msgEl.textContent = 'Fehler beim Speichern.'; msgEl.className = 'message error'; msgEl.style.display = 'block';
|
||
} finally {
|
||
btn.disabled = false; btn.textContent = 'Vorlieben speichern';
|
||
}
|
||
}
|
||
|
||
function escapeHtml(str) {
|
||
return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|