Sprint 19: Social, UX-Verbesserungen, Nerd2Noob-Hilfe
This commit is contained in:
parent
10d30bf565
commit
89d87030a2
18 changed files with 930 additions and 74 deletions
|
|
@ -45,13 +45,31 @@ window.Page_routes = (() => {
|
|||
return String(s || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
|
||||
function _emptyState(icon, title, text, cta = '') {
|
||||
return `<div class="empty-state">
|
||||
<svg class="ph-icon empty-state-icon" aria-hidden="true">
|
||||
<use href="/icons/phosphor.svg#${icon}"></use>
|
||||
</svg>
|
||||
<div class="empty-state-title">${title}</div>
|
||||
${text ? `<p class="empty-state-text">${text}</p>` : ''}
|
||||
${cta ? `<div class="empty-state-cta">${cta}</div>` : ''}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
async function init(container, appState) {
|
||||
_container = container;
|
||||
_appState = appState;
|
||||
_render();
|
||||
_loadLeaflet(); // fire & forget — bereit wenn Cards gerendert werden
|
||||
try { _userPos = await API.getLocation(); } catch {}
|
||||
_loadData();
|
||||
await _loadData();
|
||||
|
||||
// Deep-Link: /#routes?id=123 → direkt Route-Detail öffnen
|
||||
const params = new URLSearchParams((location.hash.split('?')[1] || ''));
|
||||
const deepId = params.get('id');
|
||||
if (deepId) {
|
||||
_openDetail(parseInt(deepId, 10));
|
||||
}
|
||||
}
|
||||
|
||||
async function _loadLeaflet() {
|
||||
|
|
@ -460,20 +478,12 @@ window.Page_routes = (() => {
|
|||
</div>`;
|
||||
} else {
|
||||
// Noch gar keine eigenen Routen
|
||||
grid.innerHTML = `<div class="rk-empty rk-empty--onboarding">
|
||||
<div class="rk-empty-icon">🥾</div>
|
||||
<h3 class="rk-empty-title">Deine erste Gassi-Route</h3>
|
||||
<p class="rk-empty-text">Zeichne deine Lieblingsstrecken auf — mit Streckendaten, Fotos und Hundetauglichkeit.</p>
|
||||
<div class="rk-empty-features">
|
||||
<div class="rk-empty-feature">${UI.icon('map-trifold')}<span>GPS-Aufzeichnung</span></div>
|
||||
<div class="rk-empty-feature">${UI.icon('camera')}<span>Fotos entlang der Strecke</span></div>
|
||||
<div class="rk-empty-feature"><span>🐾</span><span>Hundetauglichkeit bewerten</span></div>
|
||||
<div class="rk-empty-feature">${UI.icon('download-simple')}<span>GPX-Download für Navi</span></div>
|
||||
<div class="rk-empty-feature">${UI.icon('map-pin')}<span>Restaurants & Parkplätze</span></div>
|
||||
<div class="rk-empty-feature">${UI.icon('lock')}<span>Privat oder öffentlich</span></div>
|
||||
</div>
|
||||
<button class="btn btn-primary btn-lg" id="rk-empty-rec">${UI.icon('path')} Erste Route aufzeichnen</button>
|
||||
</div>`;
|
||||
grid.innerHTML = _emptyState(
|
||||
'map-trifold',
|
||||
'Noch keine Routen',
|
||||
'Zeichne Lieblingsrouten auf oder importiere GPX-Dateien. Teile Routen mit Freunden.',
|
||||
`<button class="btn btn-primary" id="rk-empty-rec">${UI.icon('path')} Route aufzeichnen</button>`
|
||||
);
|
||||
document.getElementById('rk-empty-rec')?.addEventListener('click', () => {
|
||||
App.navigate('map');
|
||||
setTimeout(() => window.Page_map?.startRecording?.(), 600);
|
||||
|
|
@ -688,6 +698,8 @@ window.Page_routes = (() => {
|
|||
|
||||
const footer = `
|
||||
<button type="button" class="btn btn-secondary" id="rd-gpx">${UI.icon('download-simple')} GPX</button>
|
||||
<button type="button" class="btn btn-secondary" id="rd-share" title="Route teilen">${UI.icon('share')}</button>
|
||||
<button type="button" class="btn btn-secondary" id="rd-send-friend" title="An Freund senden">${UI.icon('chat-circle-dots')} An Freund senden</button>
|
||||
${isOwn ? `<button type="button" class="btn btn-ghost" id="rd-vis" title="${route.is_public?'Privat machen':'Öffentlich machen'}">
|
||||
${route.is_public ? UI.icon('lock')+' Privat' : UI.icon('globe')+' Öffentlich'}
|
||||
</button>
|
||||
|
|
@ -700,6 +712,23 @@ window.Page_routes = (() => {
|
|||
document.getElementById('rd-close')?.addEventListener('click', UI.modal.close);
|
||||
document.getElementById('rd-gpx')?.addEventListener('click', () => _downloadGpxDirect(route));
|
||||
|
||||
// Teilen-Button
|
||||
document.getElementById('rd-share')?.addEventListener('click', () => {
|
||||
const shareUrl = location.origin + '/#routes?id=' + route.id;
|
||||
if (navigator.share) {
|
||||
navigator.share({ title: route.name, url: shareUrl }).catch(() => {});
|
||||
} else {
|
||||
navigator.clipboard.writeText(shareUrl).then(() => {
|
||||
UI.toast.success('Link kopiert!');
|
||||
}).catch(() => {
|
||||
UI.toast.error('Link konnte nicht kopiert werden.');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// An Freund senden
|
||||
document.getElementById('rd-send-friend')?.addEventListener('click', () => _openSendToFriendModal(route));
|
||||
|
||||
// Sichtbarkeit toggle
|
||||
document.getElementById('rd-vis')?.addEventListener('click', async () => {
|
||||
try {
|
||||
|
|
@ -1108,6 +1137,7 @@ window.Page_routes = (() => {
|
|||
</label>
|
||||
<label style="display:flex;align-items:center;gap:var(--space-2);cursor:pointer">
|
||||
<input type="checkbox" id="ri-public"> Öffentlich
|
||||
${UI.help('Öffentliche Routen können von allen Nutzern in der Entdecken-Ansicht gefunden werden.')}
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -1173,6 +1203,71 @@ window.Page_routes = (() => {
|
|||
return Array.from({ length: maxPts }, (_, i) => track[Math.round(i * step)]);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// An Freund senden
|
||||
// ----------------------------------------------------------
|
||||
async function _openSendToFriendModal(route) {
|
||||
const shareUrl = location.origin + '/#routes?id=' + route.id;
|
||||
|
||||
// Freunde laden
|
||||
let friends = [];
|
||||
try {
|
||||
friends = await API.friends.list();
|
||||
} catch (err) {
|
||||
UI.toast.error('Freunde konnten nicht geladen werden.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!friends.length) {
|
||||
UI.toast.info('Du hast noch keine Freunde hinzugefügt.');
|
||||
return;
|
||||
}
|
||||
|
||||
const friendRows = friends.map(f => {
|
||||
const initial = (f.name || '?')[0].toUpperCase();
|
||||
return `<div class="rk-friend-row" data-id="${f.id}" data-name="${_esc(f.name || 'Anonym')}"
|
||||
style="display:flex;align-items:center;gap:var(--space-3);padding:var(--space-3);
|
||||
cursor:pointer;border-radius:var(--radius-md);transition:background .15s"
|
||||
onmouseover="this.style.background='var(--c-surface-2)'"
|
||||
onmouseout="this.style.background=''">
|
||||
<div style="width:36px;height:36px;border-radius:50%;background:var(--c-primary);
|
||||
color:#fff;display:flex;align-items:center;justify-content:center;
|
||||
font-weight:600;flex-shrink:0">${_esc(initial)}</div>
|
||||
<span>${_esc(f.name || 'Anonym')}</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
const body = `<div id="rk-friend-list">${friendRows}</div>`;
|
||||
const footer = `<button type="button" class="btn btn-ghost" id="rsf-cancel">Abbrechen</button>`;
|
||||
|
||||
UI.modal.open({
|
||||
title: `${UI.icon('chat-circle-dots')} An Freund senden`,
|
||||
body,
|
||||
footer,
|
||||
});
|
||||
|
||||
document.getElementById('rsf-cancel')?.addEventListener('click', UI.modal.close);
|
||||
|
||||
document.getElementById('rk-friend-list')?.addEventListener('click', async e => {
|
||||
const row = e.target.closest('.rk-friend-row');
|
||||
if (!row) return;
|
||||
|
||||
const partnerId = parseInt(row.dataset.id, 10);
|
||||
const partnerName = row.dataset.name;
|
||||
|
||||
try {
|
||||
const conv = await API.chat.start(partnerId);
|
||||
const convId = conv.id;
|
||||
const text = `Ich habe eine Route für dich: ${route.name}\n${shareUrl}`;
|
||||
await API.chat.send(convId, text);
|
||||
UI.modal.close();
|
||||
UI.toast.success(`Gesendet an ${partnerName}`);
|
||||
} catch (err) {
|
||||
UI.toast.error('Senden fehlgeschlagen: ' + (err.message || 'Unbekannter Fehler'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return { init, refresh, onDogChange };
|
||||
|
||||
})();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue