Alles mögliche
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
This commit is contained in:
147
bin/main/static/dating/besucher.html
Normal file
147
bin/main/static/dating/besucher.html
Normal file
@@ -0,0 +1,147 @@
|
||||
<!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>Profilbesucher – xXx Sphere</title>
|
||||
<link rel="stylesheet" href="/css/variables.css">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<style>
|
||||
.profiles-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem; }
|
||||
@media (max-width: 500px) { .profiles-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); } }
|
||||
.profile-card { background: var(--color-card); border: 1px solid var(--color-secondary); border-radius: 12px; overflow: hidden; transition: border-color 0.15s, box-shadow 0.15s; text-decoration: none; color: var(--color-text); display: flex; flex-direction: column; }
|
||||
.profile-card:hover { border-color: var(--color-primary); box-shadow: 0 4px 18px rgba(0,0,0,0.35); }
|
||||
.profile-card-img-wrap { position: relative; width: 100%; aspect-ratio: 1; flex-shrink: 0; overflow: hidden; background: var(--color-secondary); display: flex; align-items: center; justify-content: center; font-size: 3rem; }
|
||||
.profile-card-img-wrap img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.profile-card-body { padding: 0.75rem; display: flex; flex-direction: column; gap: 0.3rem; flex: 1; }
|
||||
.profile-card-name { font-weight: 700; font-size: 1rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.profile-card-meta { display: flex; flex-wrap: wrap; gap: 0.3rem; }
|
||||
.meta-chip { padding: 0.1rem 0.45rem; border-radius: 20px; background: var(--color-secondary); font-size: 0.73rem; color: var(--color-muted); }
|
||||
.meta-chip.time { color: var(--color-primary); }
|
||||
.profile-card-desc { font-size: 0.78rem; color: var(--color-muted); line-height: 1.4; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; margin-top: 0.15rem; }
|
||||
.profile-card-skeleton { background: var(--color-card); border: 1px solid var(--color-secondary); border-radius: 12px; overflow: hidden; display: flex; flex-direction: column; }
|
||||
.skeleton-img { width: 100%; aspect-ratio: 1; background: linear-gradient(90deg, var(--color-secondary) 25%, var(--color-card) 50%, var(--color-secondary) 75%); background-size: 200% 100%; animation: shimmer 1.2s infinite; }
|
||||
.skeleton-body { padding: 0.75rem; display: flex; flex-direction: column; gap: 0.5rem; }
|
||||
.skeleton-line { height: 0.75rem; border-radius: 4px; background: linear-gradient(90deg, var(--color-secondary) 25%, var(--color-card) 50%, var(--color-secondary) 75%); background-size: 200% 100%; animation: shimmer 1.2s infinite; }
|
||||
@keyframes shimmer { to { background-position: -200% 0; } }
|
||||
.empty-state { text-align: center; padding: 3rem 1rem; color: var(--color-muted); grid-column: 1 / -1; }
|
||||
.empty-state .icon { font-size: 2.5rem; margin-bottom: 0.75rem; }
|
||||
#sentinel { height: 1px; margin-top: 1rem; }
|
||||
.results-count { font-size: 0.85rem; color: var(--color-muted); display: block; margin-bottom: 0.75rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="app">
|
||||
<div class="main">
|
||||
<div class="content">
|
||||
<h1 style="margin:0 0 0.15rem;">Profilbesucher</h1>
|
||||
<span class="results-count" id="resultsCount"></span>
|
||||
<div class="profiles-grid" id="profilesGrid"></div>
|
||||
<div id="sentinel"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/js/icons.js"></script>
|
||||
<script src="/js/sidebar.js"></script>
|
||||
<script>
|
||||
const BATCH = 12;
|
||||
let allEntries = [];
|
||||
let loadedCount = 0;
|
||||
let loading = false;
|
||||
|
||||
function relativeTime(iso) {
|
||||
const diff = Date.now() - new Date(iso).getTime();
|
||||
const m = Math.floor(diff / 60000);
|
||||
if (m < 1) return 'gerade eben';
|
||||
if (m < 60) return `vor ${m} Min.`;
|
||||
const h = Math.floor(m / 60);
|
||||
if (h < 24) return `vor ${h} Std.`;
|
||||
const d = Math.floor(h / 24);
|
||||
return `vor ${d} Tag${d !== 1 ? 'en' : ''}`;
|
||||
}
|
||||
|
||||
function esc(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
|
||||
|
||||
function appendProfiles(profiles) {
|
||||
const grid = document.getElementById('profilesGrid');
|
||||
profiles.forEach(p => {
|
||||
const entry = allEntries.find(e => e.userId === p.userId);
|
||||
const pic = p.profilePictureHq || p.profilePicture;
|
||||
const img = pic ? `<img src="data:image/png;base64,${pic}" alt="${esc(p.name)}" loading="lazy">` : '👤';
|
||||
const chips = [
|
||||
p.alter ? `<span class="meta-chip">${p.alter} J.</span>` : '',
|
||||
p.geschlecht ? `<span class="meta-chip">${esc(p.geschlecht)}</span>` : '',
|
||||
p.datingStadt ? `<span class="meta-chip">${esc(p.datingStadt)}</span>` : '',
|
||||
entry?.visitedAt ? `<span class="meta-chip time">${relativeTime(entry.visitedAt)}</span>` : '',
|
||||
].filter(Boolean).join('');
|
||||
const desc = p.beschreibung ? `<div class="profile-card-desc">${esc(p.beschreibung)}</div>` : '';
|
||||
const card = document.createElement('a');
|
||||
card.className = 'profile-card';
|
||||
card.href = `/community/benutzer.html?userId=${p.userId}`;
|
||||
card.innerHTML = `
|
||||
<div class="profile-card-img-wrap">${img}</div>
|
||||
<div class="profile-card-body">
|
||||
<div class="profile-card-name">${esc(p.name)}</div>
|
||||
<div class="profile-card-meta">${chips}</div>
|
||||
${desc}
|
||||
</div>`;
|
||||
grid.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadNextBatch() {
|
||||
if (loading || loadedCount >= allEntries.length) return;
|
||||
loading = true;
|
||||
const batch = allEntries.slice(loadedCount, loadedCount + BATCH);
|
||||
const grid = document.getElementById('profilesGrid');
|
||||
const skelDiv = document.createElement('div');
|
||||
skelDiv.style.display = 'contents';
|
||||
skelDiv.innerHTML = Array.from({length: batch.length}, () => `
|
||||
<div class="profile-card-skeleton">
|
||||
<div class="skeleton-img"></div>
|
||||
<div class="skeleton-body">
|
||||
<div class="skeleton-line" style="width:70%"></div>
|
||||
<div class="skeleton-line" style="width:50%"></div>
|
||||
</div>
|
||||
</div>`).join('');
|
||||
grid.appendChild(skelDiv);
|
||||
try {
|
||||
const res = await fetch('/dating/profiles/batch', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(batch.map(e => e.userId))
|
||||
});
|
||||
grid.removeChild(skelDiv);
|
||||
if (res.ok) appendProfiles(await res.json());
|
||||
} catch { grid.removeChild(skelDiv); }
|
||||
loadedCount += batch.length;
|
||||
loading = false;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const res = await fetch('/social/profile-visits/my-visitors');
|
||||
if (!res.ok) throw new Error();
|
||||
const visitors = await res.json();
|
||||
allEntries = visitors.map(v => ({ userId: v.userId, visitedAt: v.visitedAt }));
|
||||
document.getElementById('resultsCount').textContent =
|
||||
allEntries.length === 0 ? 'Noch keine Besucher'
|
||||
: allEntries.length + (allEntries.length === 1 ? ' Besucher' : ' Besucher');
|
||||
if (!allEntries.length) {
|
||||
document.getElementById('profilesGrid').innerHTML =
|
||||
'<div class="empty-state"><div class="icon">👀</div><p>Noch niemand hat dein Profil besucht.</p></div>';
|
||||
return;
|
||||
}
|
||||
loadNextBatch();
|
||||
} catch {
|
||||
document.getElementById('profilesGrid').innerHTML =
|
||||
'<div class="empty-state"><div class="icon">⚠️</div><p>Fehler beim Laden.</p></div>';
|
||||
}
|
||||
})();
|
||||
|
||||
new IntersectionObserver(
|
||||
e => { if (e[0].isIntersecting) loadNextBatch(); },
|
||||
{ rootMargin: '300px' }
|
||||
).observe(document.getElementById('sentinel'));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user