/* ============================================================ BAN YARO — Freunde ============================================================ */ window.Page_friends = (() => { let _container = null; let _appState = null; let _searchTimer = null; // ---------------------------------------------------------- async function init(container, appState, params = {}) { _container = container; _appState = appState; _render(params.suche || null); } function refresh() { _loadFriends(); } function onDogChange() {} // ---------------------------------------------------------- // HAUPT-RENDER // ---------------------------------------------------------- function _render(prefill = null) { if (!_appState.user) { _container.innerHTML = UI.emptyState({ icon: UI.icon('users'), title: 'Anmelden erforderlich', text: 'Melde dich an, um Freunde zu finden und Anfragen zu verwalten.', action: ``, }); return; } const myName = _appState?.user?.name || ''; const myLink = `${location.origin}/#friends?suche=${encodeURIComponent(myName)}`; _container.innerHTML = `
Dein Freundes-Link
Teile ihn — der andere tippt drauf und findet dich sofort.
banyaro.app/#friends?suche=${_esc(encodeURIComponent(myName))}

Tipp: Lass dir den Freundes-Link einer anderen Person schicken — dann klappt die Suche automatisch.

`; // Copy-Button _container.querySelector('#fr-copy-btn')?.addEventListener('click', () => { navigator.clipboard.writeText(myLink).then(() => { UI.toast.success('Link kopiert!'); }).catch(() => { UI.toast.info('Link: ' + myLink); }); }); // Share-Button (Web Share API, Fallback: Copy) _container.querySelector('#fr-share-btn')?.addEventListener('click', async () => { if (navigator.share) { try { await navigator.share({ title: `${myName} auf Ban Yaro`, text: `Füge mich auf Ban Yaro als Freund hinzu!`, url: myLink, }); } catch { /* abgebrochen */ } } else { navigator.clipboard.writeText(myLink).then(() => { UI.toast.success('Link kopiert!'); }); } }); // Suche const searchInput = _container.querySelector('#fr-search'); searchInput.addEventListener('input', e => { clearTimeout(_searchTimer); const q = e.target.value.trim(); if (q.length < 2) { _container.querySelector('#fr-search-results').innerHTML = ''; return; } _searchTimer = setTimeout(() => _doSearch(q), 380); }); // Prefill aus URL-Parameter → sofort suchen if (prefill && prefill.length >= 2) { _doSearch(prefill); } _loadFriends(); } // ---------------------------------------------------------- // DATEN LADEN // ---------------------------------------------------------- async function _loadFriends() { if (!_appState.user) return; try { const data = await API.friends.list(); _renderIncoming(data.incoming || []); _renderOutgoing(data.outgoing || []); _renderFriends(data.friends || []); _updateBadge((data.incoming || []).length); } catch { /* silent — 401 bei abgemeldeter Session */ } _loadActivity(); } async function _loadActivity() { if (!_appState.user) return; const el = _container.querySelector('#fr-activity'); if (!el) return; // Ladeindikator el.innerHTML = `
Aktivitäten
`; try { const items = await API.friends.activity(); _renderActivity(items || []); } catch { el.innerHTML = ''; } } let _activityFilter = 'alle'; let _activityAll = []; function _renderActivity(items) { _activityAll = items; const el = _container.querySelector('#fr-activity'); if (!el) return; const FILTERS = [ { key: 'alle', label: 'Alle' }, { key: 'diary', label: 'Tagebuch' }, { key: 'walk', label: 'Gassi-Treffen' }, { key: 'health', label: 'Gesundheit' }, { key: 'new_dog', label: 'Neuer Hund' }, ]; const chips = FILTERS.map(f => ` `).join(''); const filtered = _activityFilter === 'alle' ? items : items.filter(i => i.type === _activityFilter); el.innerHTML = `
Aktivitäten
${chips}
${!filtered.length ? `

Keine Einträge in dieser Kategorie.

` : `
${filtered.map(item => _activityItem(item)).join('')}
` }
`; el.querySelectorAll('[data-af]').forEach(btn => { btn.addEventListener('click', () => { _activityFilter = btn.dataset.af; _renderActivity(_activityAll); }); }); el.querySelectorAll('.fr-activity-item[data-nav]').forEach(btn => { btn.addEventListener('click', () => { const page = btn.dataset.nav; if (page) App.navigate(page); }); }); } const _ACTIVITY_PAGE = { diary: 'diary', health: 'health', walk: 'walks', new_dog: null, }; function _activityItem(item) { const ago = _timeAgo(item.created_at); const text = item.text || ''; const page = _ACTIVITY_PAGE[item.type] || ''; const dogLabel = item.dog_name ? `${_esc(item.dog_name)}` : ''; const avatar = item.dog_foto ? `${_esc(item.dog_name || '')}` : item.avatar_url ? `${_esc(item.user_name)}` : `
${_esc((item.user_name || '?')[0].toUpperCase())}
`; const tag = page ? `button type="button"` : `div`; return ` <${tag} class="fr-activity-item${page ? ' fr-activity-item--link' : ''}" ${page ? `data-nav="${page}"` : ''}>
${avatar}
${_esc(item.user_name)} ${dogLabel}
${text ? `
${_esc(text)}
` : ''}
${_esc(ago)}
`; } function _timeAgo(iso) { if (!iso) return ''; const diff = Math.floor((Date.now() - new Date(iso + (iso.endsWith('Z') ? '' : 'Z')).getTime()) / 1000); if (diff < 60) return 'Gerade eben'; if (diff < 3600) return `vor ${Math.floor(diff / 60)} Min`; if (diff < 86400) return `vor ${Math.floor(diff / 3600)} Std`; if (diff < 86400 * 7) return `vor ${Math.floor(diff / 86400)} Tagen`; return new Date(iso).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' }); } function _updateBadge(count) { const el = document.getElementById('friends-badge'); if (!el) return; el.textContent = count; el.style.display = count > 0 ? '' : 'none'; } // ---------------------------------------------------------- // EINGEHENDE ANFRAGEN // ---------------------------------------------------------- function _renderIncoming(list) { const el = _container.querySelector('#fr-incoming'); if (!list.length) { el.innerHTML = ''; return; } el.innerHTML = `
Anfragen · ${list.length}
${list.map(r => `
${_userAvatar(r.requester_name, r.dogs?.[0], r.avatar_url)}
${_esc(r.requester_name)}
${_dogPills(r.dogs, 2)}
`).join('')}
`; } // ---------------------------------------------------------- // GESENDETE ANFRAGEN // ---------------------------------------------------------- function _renderOutgoing(list) { const el = _container.querySelector('#fr-outgoing'); if (!list.length) { el.innerHTML = ''; return; } el.innerHTML = `
Gesendet
${list.map(r => `
${_esc((r.addressee_name || '?')[0].toUpperCase())}
${_esc(r.addressee_name)}
Anfrage ausstehend
`).join('')}
`; } // ---------------------------------------------------------- // FREUNDESLISTE // ---------------------------------------------------------- function _renderFriends(list) { const el = _container.querySelector('#fr-list'); if (!list.length) { el.innerHTML = _emptyState( 'users-three', 'Noch keine Freunde', 'Verbinde dich mit anderen Hundebesitzern. Teile Routen, sieh Aktivitäten und schreib Nachrichten.', `` ); el.querySelector('#fr-empty-search')?.addEventListener('click', () => { _container.querySelector('#fr-search')?.focus(); }); return; } el.innerHTML = `
Freunde · ${list.length}
${list.map(f => _friendCard(f)).join('')}
`; // Klick auf Karte → Mini-Profil el.querySelectorAll('.fr-card').forEach(card => { card.addEventListener('click', e => { if (e.target.closest('button')) return; // Buttons nicht überschreiben const fid = parseInt(card.dataset.friendId); const fname = card.dataset.friendName; const fdogs = JSON.parse(card.dataset.dogs || '[]'); const fprofile = JSON.parse(card.dataset.profile || '{}'); _showProfile(fid, fname, fdogs, fprofile); }); }); } function _friendCard(f) { const dogs = f.dogs || []; const profile = { bio: f.bio || null, wohnort: f.wohnort || null, erfahrung: f.erfahrung || null, social_link: f.social_link || null, profil_sichtbarkeit: f.profil_sichtbarkeit || null, avatar_url: f.avatar_url || null, }; return `
${_userAvatar(f.friend_name, dogs[0], f.avatar_url)}
${_esc(f.friend_name)} ${_erfahrungSpan(f.erfahrung)}
${_wohnortLine(f.wohnort)} ${_bioLine(f.bio, f.profil_sichtbarkeit)}
${dogs.length ? `
${_dogPills(dogs, 3)}
` : `
Noch kein Hund eingetragen
` }
${_dogPhotoRow(dogs)}
`; } function _dogPhotoRow(dogs) { const withPhotos = dogs.filter(d => d.foto_url); if (withPhotos.length < 2) return ''; // 1 Foto schon im Avatar, < 2 lohnt sich nicht return `
${withPhotos.slice(0, 4).map(d => `
${_esc(d.name)}
${_esc(d.name)}
`).join('')}
`; } // ---------------------------------------------------------- // MINI-PROFIL MODAL // ---------------------------------------------------------- function _showProfile(friendId, friendName, dogs, profile = {}) { const dogsHTML = dogs.length ? `
${dogs.map(d => `
${d.foto_url ? `${_esc(d.name)}` : `
🐕
` }
${_esc(d.name)}
${d.rasse ? `
${_esc(d.rasse)}
` : ''}
`).join('')}
` : `

Noch kein Hund eingetragen.

`; const profileInfoHTML = (() => { const parts = []; if (profile.wohnort) { parts.push(`
📍 ${_esc(profile.wohnort)}
`); } if (profile.erfahrung && _erfahrungBadge[profile.erfahrung]) { parts.push(`
${_erfahrungBadge[profile.erfahrung]}
`); } if (profile.bio && profile.profil_sichtbarkeit !== 'private') { parts.push(`
${_esc(profile.bio)}
`); } if (profile.social_link) { parts.push(`
${_esc(profile.social_link)}
`); } if (!parts.length) return ''; return `
${parts.join('')}
`; })(); UI.modal.open({ title: _esc(friendName), body: `
${profileInfoHTML}
${dogs.length === 1 ? 'Hund' : 'Hunde'}
${dogsHTML}
`, footer: ` `, }); document.getElementById('modal-chat-btn')?.addEventListener('click', () => { UI.modal.close(); _openChat(friendId); }); document.getElementById('modal-remove-btn')?.addEventListener('click', async () => { UI.modal.close(); await _removeFriend(friendId, friendName); }); } // ---------------------------------------------------------- // SUCHE // ---------------------------------------------------------- async function _doSearch(q) { if (!_appState.user) return; const el = _container.querySelector('#fr-search-results'); try { const results = await API.friends.search(q); if (!results.length) { el.innerHTML = `
Kein Nutzer gefunden.
`; return; } el.innerHTML = `
${results.map((u, i) => `
${_userAvatar(u.name, null, u.avatar_url)}
${_esc(u.name)} ${_erfahrungSpan(u.erfahrung)}
${_wohnortLine(u.wohnort)} ${_bioLine(u.bio, u.profil_sichtbarkeit)} ${u.dogs?.length ? `
${u.dogs.map(d => _esc(d.name) + (d.rasse ? ` · ${_esc(d.rasse)}` : '')).join('  |  ')}
` : ''}
`).join('')}
`; el.querySelectorAll('.fr-add-btn').forEach(btn => { btn.addEventListener('click', () => _sendRequest(parseInt(btn.dataset.userId), btn)); }); } catch { el.innerHTML = ''; } } // ---------------------------------------------------------- // AKTIONEN // ---------------------------------------------------------- async function _sendRequest(userId, btn) { if (!_appState.user) { App.navigate('settings'); return; } btn.disabled = true; btn.innerHTML = ``; try { await API.friends.sendRequest(userId); UI.toast.success('Freundschaftsanfrage gesendet!'); _container.querySelector('#fr-search').value = ''; _container.querySelector('#fr-search-results').innerHTML = ''; await _loadFriends(); } catch (e) { UI.toast.error(e.message || 'Fehler beim Senden.'); btn.disabled = false; btn.innerHTML = ` Anfrage`; } } async function _accept(id) { try { await API.friends.accept(id); UI.toast.success('Freundschaft angenommen!'); await _loadFriends(); } catch (e) { UI.toast.error(e.message); } } async function _decline(id) { try { await API.friends.decline(id); await _loadFriends(); } catch (e) { UI.toast.error(e.message); } } async function _cancel(id) { try { await API.friends.decline(id); await _loadFriends(); } catch (e) { UI.toast.error(e.message); } } async function _removeFriend(userId, name) { const ok = await UI.modal.confirm({ title: 'Freund entfernen?', message: `${name} wird aus deiner Freundesliste entfernt.`, confirmText: 'Entfernen', }); if (!ok) return; try { await API.friends.remove(userId); UI.toast.info('Freund entfernt.'); await _loadFriends(); } catch (e) { UI.toast.error(e.message); } } async function _openChat(userId) { if (!_appState.user) { App.navigate('settings'); return; } try { const { conversation_id } = await API.chat.start(userId); App.navigate('chat', true, { conversation_id }); } catch (e) { UI.toast.error(e.message); } } // ---------------------------------------------------------- // RENDER-HELPERS // ---------------------------------------------------------- function _userAvatar(name, firstDog, avatarUrl) { if (avatarUrl) { return `${_esc(name)}`; } if (firstDog?.foto_url) { return `${_esc(firstDog.name)}`; } return `
${_esc((name || '?')[0].toUpperCase())}
`; } const _erfahrungBadge = { einsteiger: '🐾 Einsteiger', erfahren: '⭐ Erfahren', trainer: '🎓 Trainer', zuechter: '🏅 Züchter', }; function _erfahrungSpan(erfahrung) { if (!erfahrung || !_erfahrungBadge[erfahrung]) return ''; return `${_erfahrungBadge[erfahrung]}`; } function _wohnortLine(wohnort) { if (!wohnort) return ''; return `📍 ${_esc(wohnort)}`; } function _bioLine(bio, sichtbarkeit) { if (!bio || sichtbarkeit === 'private') return ''; const text = bio.length > 120 ? bio.substring(0, 120) + '…' : bio; return `
${_esc(text)}
`; } function _dogPills(dogs, max) { if (!dogs?.length) return ''; const visible = dogs.slice(0, max); const rest = dogs.length - max; return `
${visible.map(d => ` 🐕 ${_esc(d.name)}${d.rasse ? ` · ${_esc(d.rasse)}` : ''} `).join('')} ${rest > 0 ? `+${rest}` : ''}
`; } function _esc(s) { if (!s) return ''; return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } function _emptyState(icon, title, text, cta = '') { return `
${title}
${text ? `

${text}

` : ''} ${cta ? `
${cta}
` : ''}
`; } // ---------------------------------------------------------- return { init, refresh, onDogChange, _accept, _decline, _cancel, _removeFriend, _openChat }; })();