Wetier am Cahstity game gebasterln
This commit is contained in:
@@ -33,13 +33,7 @@
|
||||
.tab-panel { display: none; }
|
||||
.tab-panel.active { display: block; }
|
||||
|
||||
/* Sektionen */
|
||||
.inv-section { margin-bottom: 2rem; }
|
||||
.inv-section-title {
|
||||
font-size: 0.8rem; font-weight: 700; color: var(--color-primary);
|
||||
text-transform: uppercase; letter-spacing: 0.06em;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
/* Liste */
|
||||
.inv-list { display: flex; flex-direction: column; gap: 0.5rem; }
|
||||
|
||||
.inv-card {
|
||||
@@ -49,21 +43,60 @@
|
||||
display: flex; align-items: center; gap: 0.9rem;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
/* Avatar mit Typ-Badge */
|
||||
.inv-avatar-wrap {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.inv-avatar {
|
||||
width: 52px; height: 52px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-secondary);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 1.4rem; flex-shrink: 0; overflow: hidden;
|
||||
font-size: 1.4rem; overflow: hidden;
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
}
|
||||
.inv-avatar img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.inv-type-badge {
|
||||
position: absolute;
|
||||
top: -6px; left: -6px;
|
||||
width: 26px; height: 26px;
|
||||
background: var(--color-card);
|
||||
border: 1px solid var(--color-secondary);
|
||||
border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 1.08rem;
|
||||
z-index: 1;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.inv-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.15rem; }
|
||||
.inv-line1 { font-size: 0.78rem; color: var(--color-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.inv-line2 { font-weight: 700; font-size: 0.95rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.inv-line3 { font-size: 0.78rem; color: var(--color-muted); }
|
||||
.empty-hint { color: var(--color-muted); font-size: 0.9rem; margin-top: 0.25rem; }
|
||||
|
||||
/* Paging */
|
||||
.paging-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
margin-top: 1rem;
|
||||
font-size: 0.88rem;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
.paging-bar button {
|
||||
width: auto;
|
||||
padding: 0.4rem 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.paging-bar button:disabled {
|
||||
opacity: 0.35;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Lockee-Einladungs-Dialog */
|
||||
.lockee-dialog-bg {
|
||||
display: none; position: fixed; inset: 0; z-index: 400;
|
||||
@@ -166,30 +199,16 @@
|
||||
|
||||
<!-- Tab: Empfangen -->
|
||||
<div id="tab-empfangen" class="tab-panel active">
|
||||
<div class="inv-section">
|
||||
<div class="inv-section-title">Lockee-Einladungen</div>
|
||||
<div class="inv-list" id="lockeeInvGrid"></div>
|
||||
<p class="empty-hint" id="lockeeInvEmpty" style="display:none;">Keine ausstehenden Lockee-Einladungen.</p>
|
||||
</div>
|
||||
<div class="inv-section">
|
||||
<div class="inv-section-title">Keyholder-Einladungen</div>
|
||||
<div class="inv-list" id="khInvGrid"></div>
|
||||
<p class="empty-hint" id="khInvEmpty" style="display:none;">Keine ausstehenden Keyholder-Einladungen.</p>
|
||||
</div>
|
||||
<div class="inv-list" id="recvList"></div>
|
||||
<p class="empty-hint" id="recvEmpty" style="display:none;">Keine ausstehenden Einladungen.</p>
|
||||
<div class="paging-bar" id="recvPaging" style="display:none;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Tab: Gesendet -->
|
||||
<div id="tab-gesendet" class="tab-panel">
|
||||
<div class="inv-section">
|
||||
<div class="inv-section-title">Gesendete Lockee-Einladungen</div>
|
||||
<div class="inv-list" id="sentLockeeGrid"></div>
|
||||
<p class="empty-hint" id="sentLockeeEmpty" style="display:none;">Keine ausstehenden gesendeten Lockee-Einladungen.</p>
|
||||
</div>
|
||||
<div class="inv-section">
|
||||
<div class="inv-section-title">Gesendete Keyholder-Einladungen</div>
|
||||
<div class="inv-list" id="sentKhGrid"></div>
|
||||
<p class="empty-hint" id="sentKhEmpty" style="display:none;">Keine ausstehenden gesendeten Keyholder-Einladungen.</p>
|
||||
</div>
|
||||
<div class="inv-list" id="sentList"></div>
|
||||
<p class="empty-hint" id="sentEmpty" style="display:none;">Keine ausstehenden gesendeten Einladungen.</p>
|
||||
<div class="paging-bar" id="sentPaging" style="display:none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -198,6 +217,7 @@
|
||||
<div class="lockee-dialog-bg" id="lockeeInviteDialog">
|
||||
<div class="lockee-dialog-overlay" onclick="closeLockeeInviteDialog()"></div>
|
||||
<div class="lockee-dialog-box">
|
||||
<button onclick="closeLockeeInviteDialog()" style="position:absolute;top:0.75rem;right:0.75rem;background:none;border:none;color:var(--color-muted);font-size:1.3rem;cursor:pointer;padding:0.2rem 0.5rem;line-height:1;" title="Schließen">✕</button>
|
||||
<div class="lockee-dialog-header">
|
||||
<div class="lockee-dialog-avatar" id="dialogAvatar">🔒</div>
|
||||
<div>
|
||||
@@ -237,6 +257,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/card-defs.js"></script>
|
||||
<script src="/js/sidebar.js"></script>
|
||||
<script src="/js/social-sidebar.js"></script>
|
||||
<script>
|
||||
@@ -248,219 +269,251 @@
|
||||
document.querySelectorAll('.tab-panel').forEach(p => p.classList.toggle('active', p.id === 'tab-' + name));
|
||||
history.replaceState(null, '', '?tab=' + name);
|
||||
}
|
||||
|
||||
// Activate tab from URL param
|
||||
const urlTab = new URLSearchParams(window.location.search).get('tab');
|
||||
if (urlTab === 'gesendet') switchTab('gesendet');
|
||||
|
||||
// ── Empfangen: Lockee-Einladungen ──
|
||||
async function loadLockeeInvitations() {
|
||||
// ── Konstanten ──
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
// ── State ──
|
||||
let recvItems = [];
|
||||
let sentItems = [];
|
||||
let recvPage = 0;
|
||||
let sentPage = 0;
|
||||
|
||||
// ── Hilfsfunktionen ──
|
||||
function fmtDate(iso) {
|
||||
const dt = new Date(iso);
|
||||
return dt.toLocaleDateString('de-DE') + ', ' + dt.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }) + ' Uhr';
|
||||
}
|
||||
|
||||
function buildAvatarHtml(picBase64, type) {
|
||||
const badge = type === 'keyholder' ? '🔑' : '🔒';
|
||||
const inner = picBase64
|
||||
? `<div class="inv-avatar"><img src="data:image/jpeg;base64,${picBase64}" alt=""></div>`
|
||||
: `<div class="inv-avatar">👤</div>`;
|
||||
return `<div class="inv-avatar-wrap"><span class="inv-type-badge">${badge}</span>${inner}</div>`;
|
||||
}
|
||||
|
||||
function renderPaging(barId, page, total, onNav) {
|
||||
const bar = document.getElementById(barId);
|
||||
if (total <= 1) { bar.style.display = 'none'; return; }
|
||||
bar.style.display = 'flex';
|
||||
bar.innerHTML = `
|
||||
<button onclick="${onNav}(${page - 1})" ${page === 0 ? 'disabled' : ''}>‹ Zurück</button>
|
||||
<span>Seite ${page + 1} von ${total}</span>
|
||||
<button onclick="${onNav}(${page + 1})" ${page >= total - 1 ? 'disabled' : ''}>Weiter ›</button>`;
|
||||
}
|
||||
|
||||
// ── Empfangen laden ──
|
||||
async function loadReceivedInvitations() {
|
||||
try {
|
||||
const res = await fetch('/lockee/invitations/mine');
|
||||
if (!res.ok) return;
|
||||
const invs = await res.json();
|
||||
const grid = document.getElementById('lockeeInvGrid');
|
||||
const empty = document.getElementById('lockeeInvEmpty');
|
||||
grid.innerHTML = '';
|
||||
if (invs.length === 0) { empty.style.display = ''; return; }
|
||||
empty.style.display = 'none';
|
||||
invs.forEach(inv => {
|
||||
const av = inv.keyholderProfilePic
|
||||
? `<div class="inv-avatar"><img src="data:image/jpeg;base64,${inv.keyholderProfilePic}" alt=""></div>`
|
||||
: `<div class="inv-avatar">👤</div>`;
|
||||
const dt = new Date(inv.createdAt);
|
||||
const createdStr = dt.toLocaleDateString('de-DE') + ', ' + dt.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }) + ' Uhr';
|
||||
const card = document.createElement('div');
|
||||
card.className = 'inv-card';
|
||||
card.id = 'lockeeinv-' + inv.token;
|
||||
card.dataset.detailsVisible = inv.detailsVisible ? '1' : '0';
|
||||
card.innerHTML = `
|
||||
${av}
|
||||
<div class="inv-body">
|
||||
<div class="inv-line1">${esc(inv.keyholderName)}</div>
|
||||
<div class="inv-line2">${esc(inv.lockName)}</div>
|
||||
<div class="inv-line3">Eingeladen am ${createdStr}</div>
|
||||
</div>
|
||||
const [lockeeRes, khRes] = await Promise.all([
|
||||
fetch('/lockee/invitations/mine'),
|
||||
fetch('/keyholder/invitations/mine')
|
||||
]);
|
||||
const lockeeInvs = lockeeRes.ok ? await lockeeRes.json() : [];
|
||||
const khInvs = khRes.ok ? await khRes.json() : [];
|
||||
|
||||
lockeeInvs.forEach(inv => { inv._type = 'lockee'; inv._otherName = inv.keyholderName; inv._otherPic = inv.keyholderProfilePic; });
|
||||
khInvs.forEach(inv => { inv._type = 'keyholder'; inv._otherName = inv.lockeeName; inv._otherPic = inv.lockeeProfilePic; });
|
||||
|
||||
recvItems = [...lockeeInvs, ...khInvs].sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
||||
recvPage = 0;
|
||||
renderRecvPage();
|
||||
} catch(e) { console.error(e); }
|
||||
}
|
||||
|
||||
function renderRecvPage() {
|
||||
const list = document.getElementById('recvList');
|
||||
const empty = document.getElementById('recvEmpty');
|
||||
list.innerHTML = '';
|
||||
|
||||
if (recvItems.length === 0) {
|
||||
empty.style.display = '';
|
||||
document.getElementById('recvPaging').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
empty.style.display = 'none';
|
||||
|
||||
const totalPages = Math.ceil(recvItems.length / PAGE_SIZE);
|
||||
const start = recvPage * PAGE_SIZE;
|
||||
const pageItems = recvItems.slice(start, start + PAGE_SIZE);
|
||||
|
||||
pageItems.forEach(inv => {
|
||||
const av = buildAvatarHtml(inv._otherPic, inv._type);
|
||||
const card = document.createElement('div');
|
||||
card.className = 'inv-card';
|
||||
card.id = 'recvinv-' + inv.token;
|
||||
if (inv._type === 'lockee') card.dataset.detailsVisible = inv.detailsVisible ? '1' : '0';
|
||||
|
||||
const typeLabel = inv._type === 'lockee' ? 'Lockee-Einladung' : 'Keyholder-Einladung';
|
||||
let actions;
|
||||
if (inv._type === 'lockee') {
|
||||
actions = `
|
||||
<div style="display:flex;flex-direction:column;gap:0.4rem;flex-shrink:0;">
|
||||
<button onclick="declineLockeeInvitation('${esc(inv.token)}', this)" style="margin:0;padding:0.45rem 1rem;font-size:0.85rem;width:auto;background:#c0392b;border:none;color:#fff;border-radius:6px;font-weight:600;cursor:pointer;">✕ Ablehnen</button>
|
||||
<button onclick="openLockeeInviteDialog('${esc(inv.token)}')" style="margin:0;padding:0.45rem 1rem;font-size:0.85rem;width:auto;background:var(--color-success,#27ae60);border:none;color:#fff;border-radius:6px;font-weight:600;cursor:pointer;">✓ Details</button>
|
||||
</div>`;
|
||||
grid.appendChild(card);
|
||||
});
|
||||
} else {
|
||||
actions = `
|
||||
<div style="display:flex;flex-direction:column;gap:0.4rem;flex-shrink:0;">
|
||||
<button onclick="declineKhInvitation('${esc(inv.token)}', this)" style="margin:0;padding:0.45rem 1rem;font-size:0.85rem;width:auto;background:#c0392b;border:none;color:#fff;border-radius:6px;font-weight:600;cursor:pointer;">✕ Ablehnen</button>
|
||||
<a href="/keyholder/invitation/${esc(inv.token)}" style="display:block;text-align:center;padding:0.45rem 1rem;font-size:0.85rem;background:var(--color-success);color:#fff;border-radius:6px;text-decoration:none;font-weight:600;">✓ Annehmen</a>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
const rolePrefix = inv._type === 'lockee' ? 'Lockee: ' : 'Keyholder: ';
|
||||
card.innerHTML = `
|
||||
${av}
|
||||
<div class="inv-body">
|
||||
<div class="inv-line1">${esc(inv._otherName)}</div>
|
||||
<div class="inv-line2">${rolePrefix}${esc(inv.lockName)}</div>
|
||||
<div class="inv-line3">${typeLabel} · ${fmtDate(inv.createdAt)}</div>
|
||||
</div>
|
||||
${actions}`;
|
||||
list.appendChild(card);
|
||||
});
|
||||
|
||||
renderPaging('recvPaging', recvPage, totalPages, 'goRecvPage');
|
||||
}
|
||||
|
||||
function goRecvPage(page) {
|
||||
const total = Math.ceil(recvItems.length / PAGE_SIZE);
|
||||
if (page < 0 || page >= total) return;
|
||||
recvPage = page;
|
||||
renderRecvPage();
|
||||
document.getElementById('recvList').scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
|
||||
function removeRecvItem(token) {
|
||||
recvItems = recvItems.filter(i => i.token !== token);
|
||||
const total = Math.ceil(recvItems.length / PAGE_SIZE);
|
||||
if (recvPage >= total && recvPage > 0) recvPage = total - 1;
|
||||
renderRecvPage();
|
||||
}
|
||||
|
||||
// ── Gesendet laden ──
|
||||
async function loadSentInvitations() {
|
||||
try {
|
||||
const [lockeeRes, khRes] = await Promise.all([
|
||||
fetch('/lockee/invitations/sent'),
|
||||
fetch('/keyholder/invitations/sent')
|
||||
]);
|
||||
const lockeeInvs = lockeeRes.ok ? await lockeeRes.json() : [];
|
||||
const khInvs = khRes.ok ? await khRes.json() : [];
|
||||
|
||||
lockeeInvs.forEach(inv => { inv._type = 'lockee'; inv._otherName = inv.lockeeName; inv._otherPic = inv.lockeeProfilePic; });
|
||||
khInvs.forEach(inv => { inv._type = 'keyholder'; inv._otherName = inv.keyholderName; inv._otherPic = inv.keyholderProfilePic; });
|
||||
|
||||
sentItems = [...lockeeInvs, ...khInvs].sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
||||
sentPage = 0;
|
||||
renderSentPage();
|
||||
} catch(e) { console.error(e); }
|
||||
}
|
||||
|
||||
function renderSentPage() {
|
||||
const list = document.getElementById('sentList');
|
||||
const empty = document.getElementById('sentEmpty');
|
||||
list.innerHTML = '';
|
||||
|
||||
if (sentItems.length === 0) {
|
||||
empty.style.display = '';
|
||||
document.getElementById('sentPaging').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
empty.style.display = 'none';
|
||||
|
||||
const totalPages = Math.ceil(sentItems.length / PAGE_SIZE);
|
||||
const start = sentPage * PAGE_SIZE;
|
||||
const pageItems = sentItems.slice(start, start + PAGE_SIZE);
|
||||
|
||||
pageItems.forEach(inv => {
|
||||
const av = buildAvatarHtml(inv._otherPic, inv._type);
|
||||
const card = document.createElement('div');
|
||||
card.className = 'inv-card';
|
||||
card.id = 'sentinv-' + inv.token;
|
||||
|
||||
const typeLabel = inv._type === 'lockee' ? 'Lockee-Einladung' : 'Keyholder-Einladung';
|
||||
let extra = '';
|
||||
if (inv._type === 'lockee') {
|
||||
extra = inv.detailsVisible
|
||||
? ' <span style="font-size:0.72rem;">👁 Details sichtbar</span>'
|
||||
: ' <span style="font-size:0.72rem;">🙈 Details verborgen</span>';
|
||||
}
|
||||
|
||||
const rolePrefix2 = inv._type === 'lockee' ? 'Lockee: ' : 'Keyholder: ';
|
||||
card.innerHTML = `
|
||||
${av}
|
||||
<div class="inv-body">
|
||||
<div class="inv-line1">${esc(inv._otherName)}</div>
|
||||
<div class="inv-line2">${rolePrefix2}${esc(inv.lockName)}</div>
|
||||
<div class="inv-line3">${typeLabel} · ${fmtDate(inv.createdAt)}${extra}</div>
|
||||
</div>
|
||||
<div style="flex-shrink:0;">
|
||||
<button onclick="cancelSentInvitation('${esc(inv.token)}', '${inv._type}', this)" style="margin:0;padding:0.45rem 1rem;font-size:0.85rem;width:auto;background:#c0392b;border:none;color:#fff;border-radius:6px;font-weight:600;cursor:pointer;">✕ Zurückziehen</button>
|
||||
</div>`;
|
||||
list.appendChild(card);
|
||||
});
|
||||
|
||||
renderPaging('sentPaging', sentPage, totalPages, 'goSentPage');
|
||||
}
|
||||
|
||||
function goSentPage(page) {
|
||||
const total = Math.ceil(sentItems.length / PAGE_SIZE);
|
||||
if (page < 0 || page >= total) return;
|
||||
sentPage = page;
|
||||
renderSentPage();
|
||||
document.getElementById('sentList').scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
|
||||
function removeSentItem(token) {
|
||||
sentItems = sentItems.filter(i => i.token !== token);
|
||||
const total = Math.ceil(sentItems.length / PAGE_SIZE);
|
||||
if (sentPage >= total && sentPage > 0) sentPage = total - 1;
|
||||
renderSentPage();
|
||||
}
|
||||
|
||||
// ── Aktionen: Empfangen ──
|
||||
async function declineLockeeInvitation(token, btn) {
|
||||
if (!confirm('Bist du sicher, dass du diese Einladung ablehnen möchtest?')) return;
|
||||
btn.disabled = true;
|
||||
try {
|
||||
const res = await fetch('/lockee/invitation/' + encodeURIComponent(token), { method: 'DELETE' });
|
||||
if (res.ok || res.status === 204) {
|
||||
document.getElementById('lockeeinv-' + token)?.remove();
|
||||
const grid = document.getElementById('lockeeInvGrid');
|
||||
if (grid.children.length === 0) document.getElementById('lockeeInvEmpty').style.display = '';
|
||||
} else { btn.disabled = false; }
|
||||
if (res.ok || res.status === 204) { removeRecvItem(token); }
|
||||
else { btn.disabled = false; }
|
||||
} catch(e) { btn.disabled = false; }
|
||||
}
|
||||
|
||||
// ── Empfangen: Keyholder-Einladungen ──
|
||||
async function loadKeyholderInvitations() {
|
||||
try {
|
||||
const res = await fetch('/keyholder/invitations/mine');
|
||||
if (!res.ok) return;
|
||||
const invs = await res.json();
|
||||
const grid = document.getElementById('khInvGrid');
|
||||
const empty = document.getElementById('khInvEmpty');
|
||||
grid.innerHTML = '';
|
||||
if (invs.length === 0) { empty.style.display = ''; return; }
|
||||
empty.style.display = 'none';
|
||||
invs.forEach(inv => {
|
||||
const av = inv.lockeeProfilePic
|
||||
? `<div class="inv-avatar"><img src="data:image/jpeg;base64,${inv.lockeeProfilePic}" alt=""></div>`
|
||||
: `<div class="inv-avatar">👤</div>`;
|
||||
const dt = new Date(inv.createdAt);
|
||||
const createdStr = dt.toLocaleDateString('de-DE') + ', ' + dt.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }) + ' Uhr';
|
||||
const card = document.createElement('div');
|
||||
card.className = 'inv-card';
|
||||
card.id = 'khinv-' + inv.token;
|
||||
card.innerHTML = `
|
||||
${av}
|
||||
<div class="inv-body">
|
||||
<div class="inv-line1">${esc(inv.lockeeName)}</div>
|
||||
<div class="inv-line2">${esc(inv.lockName)}</div>
|
||||
<div class="inv-line3">Eingeladen am ${createdStr}</div>
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:0.4rem;flex-shrink:0;">
|
||||
<button onclick="declineKhInvitation('${esc(inv.token)}', this)" style="margin:0;padding:0.45rem 1rem;font-size:0.85rem;width:auto;background:#c0392b;border:none;color:#fff;border-radius:6px;font-weight:600;cursor:pointer;">✕ Ablehnen</button>
|
||||
<a href="/keyholder/invitation/${esc(inv.token)}" style="display:block;text-align:center;padding:0.45rem 1rem;font-size:0.85rem;background:var(--color-success);color:#fff;border-radius:6px;text-decoration:none;font-weight:600;">✓ Annehmen</a>
|
||||
</div>`;
|
||||
grid.appendChild(card);
|
||||
});
|
||||
} catch(e) { console.error(e); }
|
||||
}
|
||||
|
||||
async function declineKhInvitation(token, btn) {
|
||||
if (!confirm('Bist du sicher, dass du diese Einladung ablehnen möchtest?')) return;
|
||||
btn.disabled = true;
|
||||
try {
|
||||
const res = await fetch('/keyholder/invitations/mine/' + encodeURIComponent(token), { method: 'DELETE' });
|
||||
if (res.ok || res.status === 204) {
|
||||
document.getElementById('khinv-' + token)?.remove();
|
||||
const grid = document.getElementById('khInvGrid');
|
||||
if (grid.children.length === 0) document.getElementById('khInvEmpty').style.display = '';
|
||||
} else { btn.disabled = false; }
|
||||
if (res.ok || res.status === 204) { removeRecvItem(token); }
|
||||
else { btn.disabled = false; }
|
||||
} catch(e) { btn.disabled = false; }
|
||||
}
|
||||
|
||||
// ── Gesendet: Lockee-Einladungen ──
|
||||
async function loadSentLockeeInvitations() {
|
||||
try {
|
||||
const res = await fetch('/lockee/invitations/sent');
|
||||
if (!res.ok) return;
|
||||
const invs = await res.json();
|
||||
const grid = document.getElementById('sentLockeeGrid');
|
||||
const empty = document.getElementById('sentLockeeEmpty');
|
||||
grid.innerHTML = '';
|
||||
if (invs.length === 0) { empty.style.display = ''; return; }
|
||||
empty.style.display = 'none';
|
||||
invs.forEach(inv => {
|
||||
const av = inv.lockeeProfilePic
|
||||
? `<div class="inv-avatar"><img src="data:image/jpeg;base64,${inv.lockeeProfilePic}" alt=""></div>`
|
||||
: `<div class="inv-avatar">👤</div>`;
|
||||
const dt = new Date(inv.createdAt);
|
||||
const createdStr = dt.toLocaleDateString('de-DE') + ', ' + dt.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }) + ' Uhr';
|
||||
const badge = inv.detailsVisible
|
||||
? '<span style="font-size:0.72rem;">👁 Details sichtbar</span>'
|
||||
: '<span style="font-size:0.72rem;">🙈 Details verborgen</span>';
|
||||
const card = document.createElement('div');
|
||||
card.className = 'inv-card';
|
||||
card.id = 'sentlockeeinv-' + inv.token;
|
||||
card.innerHTML = `
|
||||
${av}
|
||||
<div class="inv-body">
|
||||
<div class="inv-line1">${esc(inv.lockeeName)}</div>
|
||||
<div class="inv-line2">${esc(inv.lockName)}</div>
|
||||
<div class="inv-line3">Gesendet am ${createdStr} ${badge}</div>
|
||||
</div>
|
||||
<div style="flex-shrink:0;">
|
||||
<button onclick="cancelSentLockeeInvitation('${esc(inv.token)}', this)" style="margin:0;padding:0.45rem 1rem;font-size:0.85rem;width:auto;background:#c0392b;border:none;color:#fff;border-radius:6px;font-weight:600;cursor:pointer;">✕ Zurückziehen</button>
|
||||
</div>`;
|
||||
grid.appendChild(card);
|
||||
});
|
||||
} catch(e) { console.error(e); }
|
||||
}
|
||||
|
||||
async function cancelSentLockeeInvitation(token, btn) {
|
||||
if (!confirm('Einladung zurückziehen? Das Lock wird gelöscht und der Lockee wird benachrichtigt.')) return;
|
||||
// ── Aktionen: Gesendet ──
|
||||
async function cancelSentInvitation(token, type, btn) {
|
||||
const msg = type === 'lockee'
|
||||
? 'Einladung zurückziehen? Das Lock wird gelöscht und der Lockee wird benachrichtigt.'
|
||||
: 'Keyholder-Einladung zurückziehen? Der Keyholder wird benachrichtigt.';
|
||||
if (!confirm(msg)) return;
|
||||
btn.disabled = true;
|
||||
const url = type === 'lockee'
|
||||
? '/lockee/invitations/sent/' + encodeURIComponent(token)
|
||||
: '/keyholder/invitations/sent/' + encodeURIComponent(token);
|
||||
try {
|
||||
const res = await fetch('/lockee/invitations/sent/' + encodeURIComponent(token), { method: 'DELETE' });
|
||||
if (res.ok || res.status === 204) {
|
||||
document.getElementById('sentlockeeinv-' + token)?.remove();
|
||||
const grid = document.getElementById('sentLockeeGrid');
|
||||
if (grid.children.length === 0) document.getElementById('sentLockeeEmpty').style.display = '';
|
||||
} else { btn.disabled = false; }
|
||||
} catch(e) { btn.disabled = false; }
|
||||
}
|
||||
|
||||
// ── Gesendet: Keyholder-Einladungen ──
|
||||
async function loadSentKeyholderInvitations() {
|
||||
try {
|
||||
const res = await fetch('/keyholder/invitations/sent');
|
||||
if (!res.ok) return;
|
||||
const invs = await res.json();
|
||||
const grid = document.getElementById('sentKhGrid');
|
||||
const empty = document.getElementById('sentKhEmpty');
|
||||
grid.innerHTML = '';
|
||||
if (invs.length === 0) { empty.style.display = ''; return; }
|
||||
empty.style.display = 'none';
|
||||
invs.forEach(inv => {
|
||||
const av = inv.keyholderProfilePic
|
||||
? `<div class="inv-avatar"><img src="data:image/jpeg;base64,${inv.keyholderProfilePic}" alt=""></div>`
|
||||
: `<div class="inv-avatar">👤</div>`;
|
||||
const dt = new Date(inv.createdAt);
|
||||
const createdStr = dt.toLocaleDateString('de-DE') + ', ' + dt.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }) + ' Uhr';
|
||||
const card = document.createElement('div');
|
||||
card.className = 'inv-card';
|
||||
card.id = 'sentkhinv-' + inv.token;
|
||||
card.innerHTML = `
|
||||
${av}
|
||||
<div class="inv-body">
|
||||
<div class="inv-line1">${esc(inv.keyholderName)}</div>
|
||||
<div class="inv-line2">${esc(inv.lockName)}</div>
|
||||
<div class="inv-line3">Gesendet am ${createdStr}</div>
|
||||
</div>
|
||||
<div style="flex-shrink:0;">
|
||||
<button onclick="cancelSentKhInvitation('${esc(inv.token)}', this)" style="margin:0;padding:0.45rem 1rem;font-size:0.85rem;width:auto;background:#c0392b;border:none;color:#fff;border-radius:6px;font-weight:600;cursor:pointer;">✕ Zurückziehen</button>
|
||||
</div>`;
|
||||
grid.appendChild(card);
|
||||
});
|
||||
} catch(e) { console.error(e); }
|
||||
}
|
||||
|
||||
async function cancelSentKhInvitation(token, btn) {
|
||||
if (!confirm('Keyholder-Einladung zurückziehen? Der Keyholder wird benachrichtigt.')) return;
|
||||
btn.disabled = true;
|
||||
try {
|
||||
const res = await fetch('/keyholder/invitations/sent/' + encodeURIComponent(token), { method: 'DELETE' });
|
||||
if (res.ok || res.status === 204) {
|
||||
document.getElementById('sentkhinv-' + token)?.remove();
|
||||
const grid = document.getElementById('sentKhGrid');
|
||||
if (grid.children.length === 0) document.getElementById('sentKhEmpty').style.display = '';
|
||||
} else { btn.disabled = false; }
|
||||
const res = await fetch(url, { method: 'DELETE' });
|
||||
if (res.ok || res.status === 204) { removeSentItem(token); }
|
||||
else { btn.disabled = false; }
|
||||
} catch(e) { btn.disabled = false; }
|
||||
}
|
||||
|
||||
// ── Lockee-Einladungs-Dialog ──
|
||||
const CARD_DEFS_DIALOG = [
|
||||
{ id: 'RED', img: '/img/card_red.png', name: 'Rot' },
|
||||
{ id: 'GREEN', img: '/img/card_green.png', name: 'Grün' },
|
||||
{ id: 'YELLOW', img: '/img/card_yellow.png', name: 'Gelb' },
|
||||
{ id: 'TASK', img: '/img/card_task.png', name: 'Aufgabe' },
|
||||
{ id: 'FREEZE', img: '/img/card_freeze.png', name: 'Freeze' },
|
||||
{ id: 'RESET', img: '/img/card_reset.png', name: 'Reset' },
|
||||
{ id: 'DOUBLE_UP', img: '/img/card_doubleup.png', name: 'Double Up' },
|
||||
];
|
||||
// CARD_DEFS wird von /js/card-defs.js bereitgestellt.
|
||||
|
||||
function fmtMinutes(min) {
|
||||
if (!min) return '–';
|
||||
@@ -483,7 +536,7 @@
|
||||
}
|
||||
const cardCounts = inv.cardCounts || {};
|
||||
const totalCards = Object.values(cardCounts).reduce((a, b) => a + b, 0);
|
||||
const cardsHtml = CARD_DEFS_DIALOG
|
||||
const cardsHtml = CARD_DEFS
|
||||
.filter(c => cardCounts[c.id] > 0)
|
||||
.map(c => `<div class="lock-details-card-item">
|
||||
<img src="${c.img}" alt="${c.name}">
|
||||
@@ -513,7 +566,7 @@
|
||||
document.getElementById('dialogDetailsArea').innerHTML = '<div style="color:var(--color-muted);font-size:0.85rem;">Lade Details…</div>';
|
||||
document.getElementById('lockeeInviteDialog').classList.add('open');
|
||||
|
||||
const card = document.getElementById('lockeeinv-' + token);
|
||||
const card = document.getElementById('recvinv-' + token);
|
||||
const line1 = card?.querySelector('.inv-line1')?.textContent || '';
|
||||
const line2 = card?.querySelector('.inv-line2')?.textContent || '';
|
||||
const line3 = card?.querySelector('.inv-line3')?.textContent || '';
|
||||
@@ -570,9 +623,7 @@
|
||||
}
|
||||
const data = await res.json();
|
||||
document.getElementById('lockeeInviteDialog').classList.remove('open');
|
||||
document.getElementById('lockeeinv-' + activeDialogToken)?.remove();
|
||||
const grid = document.getElementById('lockeeInvGrid');
|
||||
if (grid.children.length === 0) document.getElementById('lockeeInvEmpty').style.display = '';
|
||||
removeRecvItem(activeDialogToken);
|
||||
showUnlockCodeModal(data.unlockCode, data.lockId);
|
||||
} catch(e) {
|
||||
acceptBtn.disabled = false;
|
||||
@@ -588,9 +639,7 @@
|
||||
try {
|
||||
const res = await fetch('/lockee/invitation/' + encodeURIComponent(activeDialogToken), { method: 'DELETE' });
|
||||
if (res.ok || res.status === 204) {
|
||||
document.getElementById('lockeeinv-' + activeDialogToken)?.remove();
|
||||
const grid = document.getElementById('lockeeInvGrid');
|
||||
if (grid.children.length === 0) document.getElementById('lockeeInvEmpty').style.display = '';
|
||||
removeRecvItem(activeDialogToken);
|
||||
closeLockeeInviteDialog();
|
||||
} else {
|
||||
declineBtn.disabled = false;
|
||||
@@ -608,7 +657,7 @@
|
||||
// ── Entsperrcode-Modal ──
|
||||
function showUnlockCodeModal(code, lockId) {
|
||||
document.getElementById('unlockCodeDisplay').textContent = code;
|
||||
const url = '/sessionchastityingame.html?lockId=' + lockId;
|
||||
const url = '/activelock.html?lockId=' + lockId;
|
||||
const btn = document.getElementById('unlockModalBtn');
|
||||
btn.onclick = () => startCodeScramble(code, url);
|
||||
document.getElementById('unlockModal').classList.add('open');
|
||||
@@ -654,11 +703,16 @@
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// ── Esc schließt Dialog ──
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Escape' && document.getElementById('lockeeInviteDialog').classList.contains('open')) {
|
||||
closeLockeeInviteDialog();
|
||||
}
|
||||
});
|
||||
|
||||
// ── Alles laden ──
|
||||
loadLockeeInvitations();
|
||||
loadKeyholderInvitations();
|
||||
loadSentLockeeInvitations();
|
||||
loadSentKeyholderInvitations();
|
||||
loadReceivedInvitations();
|
||||
loadSentInvitations();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user