- Freundschaften (pending/accepted), Nutzersuche, Anfragen per Push - Direktnachrichten mit Polling, iMessage-Stil, Deep-Links aus Push - Alle Seiten (map, places, diary, health, dog-profile, sitting, knigge, forum, wiki, walks) vollständig auf Phosphor-Icons migriert - Wikidata-Rassen-Scraper (~833 neue Rassen, lokal gespiegelte Fotos) - TheDogAPI lokal gespiegelt (169 Rassen + Fotos) - Quiz-Result-Cards horizontal (korrekte Bildproportionen) - SW by-v89
282 lines
9.4 KiB
JavaScript
282 lines
9.4 KiB
JavaScript
/* ============================================================
|
|
BAN YARO — Freunde-Seite
|
|
============================================================ */
|
|
|
|
window.Page_friends = (() => {
|
|
|
|
let _container = null;
|
|
let _searchTimer = null;
|
|
|
|
// ----------------------------------------------------------
|
|
function init(container) {
|
|
_container = container;
|
|
render();
|
|
}
|
|
|
|
// ----------------------------------------------------------
|
|
async function render() {
|
|
_container.innerHTML = `
|
|
<div style="padding:var(--space-4)">
|
|
<h2 style="font-size:var(--text-xl);font-weight:var(--weight-bold);margin-bottom:var(--space-4)">
|
|
Freunde
|
|
</h2>
|
|
|
|
<!-- Suche -->
|
|
<div class="friends-search-row">
|
|
<input id="fr-search" class="form-input" type="search"
|
|
placeholder="Namen suchen…" autocomplete="off" style="flex:1">
|
|
</div>
|
|
<div id="fr-search-results"></div>
|
|
|
|
<!-- Incoming requests -->
|
|
<div id="fr-incoming"></div>
|
|
|
|
<!-- Outgoing -->
|
|
<div id="fr-outgoing"></div>
|
|
|
|
<!-- Friends list -->
|
|
<div id="fr-list"></div>
|
|
</div>
|
|
`;
|
|
|
|
document.getElementById('fr-search').addEventListener('input', e => {
|
|
clearTimeout(_searchTimer);
|
|
_searchTimer = setTimeout(() => _doSearch(e.target.value.trim()), 400);
|
|
});
|
|
|
|
await _loadFriends();
|
|
}
|
|
|
|
// ----------------------------------------------------------
|
|
async function _loadFriends() {
|
|
try {
|
|
const data = await API.friends.list();
|
|
_renderIncoming(data.incoming);
|
|
_renderOutgoing(data.outgoing);
|
|
_renderFriends(data.friends);
|
|
_updateBadge(data.incoming.length);
|
|
} catch (e) {
|
|
if (e.status === 401) {
|
|
document.getElementById('fr-list').innerHTML =
|
|
`<div class="empty-state"><p>Bitte melde dich an, um Freunde zu verwalten.</p></div>`;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------
|
|
function _updateBadge(count) {
|
|
const el = document.getElementById('friends-badge');
|
|
if (!el) return;
|
|
el.textContent = count;
|
|
el.style.display = count > 0 ? '' : 'none';
|
|
}
|
|
|
|
// ----------------------------------------------------------
|
|
function _renderIncoming(list) {
|
|
const el = document.getElementById('fr-incoming');
|
|
if (!list.length) { el.innerHTML = ''; return; }
|
|
el.innerHTML = `
|
|
<h3 style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
|
color:var(--c-text-secondary);margin-bottom:var(--space-2)">
|
|
Anfragen (${list.length})
|
|
</h3>
|
|
${list.map(r => `
|
|
<div class="friend-request-card">
|
|
<div class="friend-avatar">${_initial(r.requester_name)}</div>
|
|
<div style="flex:1">
|
|
<div style="font-weight:var(--weight-semibold)">${_esc(r.requester_name)}</div>
|
|
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">möchte mit dir befreundet sein</div>
|
|
</div>
|
|
<div class="friend-item-actions">
|
|
<button class="btn btn-primary btn-sm"
|
|
onclick="Page_friends._accept(${r.id})">
|
|
<svg class="ph-icon"><use href="/icons/phosphor.svg#check"></use></svg>
|
|
</button>
|
|
<button class="btn btn-ghost btn-sm"
|
|
onclick="Page_friends._decline(${r.id})">
|
|
<svg class="ph-icon"><use href="/icons/phosphor.svg#x"></use></svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`).join('')}
|
|
`;
|
|
}
|
|
|
|
// ----------------------------------------------------------
|
|
function _renderOutgoing(list) {
|
|
const el = document.getElementById('fr-outgoing');
|
|
if (!list.length) { el.innerHTML = ''; return; }
|
|
el.innerHTML = `
|
|
<h3 style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
|
color:var(--c-text-secondary);margin:var(--space-4) 0 var(--space-2)">
|
|
Gesendete Anfragen
|
|
</h3>
|
|
${list.map(r => `
|
|
<div class="friend-item">
|
|
<div class="friend-avatar">${_initial(r.addressee_name)}</div>
|
|
<div class="friend-item-name">${_esc(r.addressee_name)}</div>
|
|
<span style="font-size:var(--text-xs);color:var(--c-text-muted)">ausstehend</span>
|
|
<button class="btn btn-ghost btn-sm"
|
|
onclick="Page_friends._cancel(${r.id})"
|
|
title="Anfrage zurückziehen">
|
|
<svg class="ph-icon"><use href="/icons/phosphor.svg#x"></use></svg>
|
|
</button>
|
|
</div>
|
|
`).join('')}
|
|
`;
|
|
}
|
|
|
|
// ----------------------------------------------------------
|
|
function _renderFriends(list) {
|
|
const el = document.getElementById('fr-list');
|
|
if (!list.length) {
|
|
el.innerHTML = `
|
|
<div class="empty-state" style="margin-top:var(--space-6)">
|
|
<svg class="ph-icon" style="font-size:3rem;opacity:0.3"><use href="/icons/phosphor.svg#users"></use></svg>
|
|
<p style="margin-top:var(--space-3);color:var(--c-text-muted)">
|
|
Noch keine Freunde. Suche oben nach Nutzern!
|
|
</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
el.innerHTML = `
|
|
<h3 style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
|
color:var(--c-text-secondary);margin:var(--space-4) 0 var(--space-2)">
|
|
Freunde (${list.length})
|
|
</h3>
|
|
${list.map(f => `
|
|
<div class="friend-item">
|
|
<div class="friend-avatar">${_initial(f.friend_name)}</div>
|
|
<div class="friend-item-name">${_esc(f.friend_name)}</div>
|
|
<div class="friend-item-actions">
|
|
<button class="btn btn-ghost btn-sm"
|
|
onclick="Page_friends._openChat(${f.friend_id})"
|
|
title="Nachricht senden">
|
|
<svg class="ph-icon"><use href="/icons/phosphor.svg#chat-circle-dots"></use></svg>
|
|
</button>
|
|
<button class="btn btn-ghost btn-sm"
|
|
onclick="Page_friends._removeFriend(${f.friend_id}, '${_esc(f.friend_name)}')"
|
|
title="Freund entfernen">
|
|
<svg class="ph-icon"><use href="/icons/phosphor.svg#user-minus"></use></svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`).join('')}
|
|
`;
|
|
}
|
|
|
|
// ----------------------------------------------------------
|
|
async function _doSearch(q) {
|
|
const el = document.getElementById('fr-search-results');
|
|
if (q.length < 2) { el.innerHTML = ''; return; }
|
|
try {
|
|
const results = await API.friends.search(q);
|
|
if (!results.length) {
|
|
el.innerHTML = `<div class="friends-search-results">
|
|
<div style="padding:var(--space-3) var(--space-4);color:var(--c-text-muted);font-size:var(--text-sm)">
|
|
Keine Nutzer gefunden.
|
|
</div>
|
|
</div>`;
|
|
return;
|
|
}
|
|
el.innerHTML = `
|
|
<div class="friends-search-results">
|
|
${results.map(u => `
|
|
<div class="friend-result-item">
|
|
<div class="friend-avatar">${_initial(u.name)}</div>
|
|
<div style="flex:1;font-size:var(--text-sm)">${_esc(u.name)}</div>
|
|
<button class="btn btn-primary btn-sm"
|
|
onclick="Page_friends._sendRequest(${u.id}, this)">
|
|
<svg class="ph-icon"><use href="/icons/phosphor.svg#user-plus"></use></svg>
|
|
Anfrage
|
|
</button>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
`;
|
|
} catch (e) {
|
|
el.innerHTML = '';
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------
|
|
async function _sendRequest(userId, btn) {
|
|
btn.disabled = true;
|
|
try {
|
|
await API.friends.sendRequest(userId);
|
|
UI.toast('Freundschaftsanfrage gesendet!', 'success');
|
|
document.getElementById('fr-search').value = '';
|
|
document.getElementById('fr-search-results').innerHTML = '';
|
|
await _loadFriends();
|
|
} catch (e) {
|
|
UI.toast(e.message, 'danger');
|
|
btn.disabled = false;
|
|
}
|
|
}
|
|
|
|
async function _accept(id) {
|
|
try {
|
|
await API.friends.accept(id);
|
|
UI.toast('Freundschaft angenommen!', 'success');
|
|
await _loadFriends();
|
|
} catch (e) {
|
|
UI.toast(e.message, 'danger');
|
|
}
|
|
}
|
|
|
|
async function _decline(id) {
|
|
try {
|
|
await API.friends.decline(id);
|
|
await _loadFriends();
|
|
} catch (e) {
|
|
UI.toast(e.message, 'danger');
|
|
}
|
|
}
|
|
|
|
async function _cancel(id) {
|
|
try {
|
|
await API.friends.decline(id);
|
|
await _loadFriends();
|
|
} catch (e) {
|
|
UI.toast(e.message, 'danger');
|
|
}
|
|
}
|
|
|
|
async function _removeFriend(userId, name) {
|
|
if (!confirm(`${name} als Freund entfernen?`)) return;
|
|
try {
|
|
await API.friends.remove(userId);
|
|
UI.toast('Freund entfernt.', 'info');
|
|
await _loadFriends();
|
|
} catch (e) {
|
|
UI.toast(e.message, 'danger');
|
|
}
|
|
}
|
|
|
|
async function _openChat(userId) {
|
|
try {
|
|
const { conversation_id } = await API.chat.start(userId);
|
|
App.navigate('chat', true, { conversation_id });
|
|
} catch (e) {
|
|
UI.toast(e.message, 'danger');
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------
|
|
function _initial(name) {
|
|
return (name || '?')[0].toUpperCase();
|
|
}
|
|
|
|
function _esc(s) {
|
|
if (!s) return '';
|
|
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
}
|
|
|
|
// ----------------------------------------------------------
|
|
return {
|
|
init,
|
|
_sendRequest, _accept, _decline, _cancel, _removeFriend, _openChat,
|
|
};
|
|
|
|
})();
|