/* ============================================================ BAN YARO — Einstellungen / Account Login, Registrierung, Logout, Account-Info. ============================================================ */ window.Page_settings = (() => { let _container = null; let _appState = null; let _mode = 'login'; // 'login' | 'register' // ---------------------------------------------------------- // HUNDEPASSPHRASE — sicheres Passwort aus Hundewelt // ---------------------------------------------------------- const _PW_WOERTER = [ // Rassen 'Labrador','Pudel','Beagle','Husky','Dackel','Spitz','Mops','Boxer', 'Collie','Setter','Pointer','Retriever','Shepherd','Terrier','Welpe', // Körper & Natur 'Pfote','Schwanz','Schnauze','Schnurrbart','Fell','Nase','Ohr', // Aktivität 'Gassi','Laufen','Bellen','Springen','Graben','Schnüffeln','Spielen', 'Apportieren','Schwimmen','Hecheln','Wackeln','Toben', // Gegenstände 'Leckerli','Leine','Halsband','Ball','Napf','Knochen','Frisbee', 'Körbchen','Bürste','Leine','Stöckchen','Kauspielzeug', // Orte & Personen 'Wiese','Wald','Park','Bach','Pfütze','Tierarzt','Züchter', // Eigenschaften 'Treu','Tapfer','Mutig','Flauschig','Verspielt','Neugierig', 'Wachsam','Flink','Sanft','Lieb', // Geräusche & Aktionen 'Wuff','Jaulen','Schnuppern','Wedeln','Gähnen','Strecken', // Futter 'Trockenfutter','Nassfutter','Kausnack','Futternapf', ]; function _genPassphrase() { const pick = () => _PW_WOERTER[Math.floor(Math.random() * _PW_WOERTER.length)]; const num = Math.floor(Math.random() * 90) + 10; // 2-stellig // 3 zufällige Wörter + Zahl, mit Bindestrich const words = []; while (words.length < 3) { const w = pick(); if (!words.includes(w)) words.push(w); } return words.join('-') + '-' + num; } // ---------------------------------------------------------- // INIT / REFRESH // ---------------------------------------------------------- async function init(container, appState) { _container = container; _appState = appState; _render(); } function refresh() { _render(); } // ---------------------------------------------------------- // RENDER // ---------------------------------------------------------- function _render() { if (_appState.user) { _renderAccount(); } else { _renderAuth(_mode); } } // ---------------------------------------------------------- // EINGELOGGT — Account-Übersicht // ---------------------------------------------------------- async function _renderAccount() { const u = _appState.user; // Avatar: Bild oder Buchstabe const avatarInner = u.avatar_url ? `Avatar` : _esc(u.name.charAt(0).toUpperCase()); // Mitglied seit const memberSince = (() => { if (!u.created_at) return ''; const d = new Date(u.created_at); return d.toLocaleDateString('de-DE', { month: 'long', year: 'numeric' }); })(); // Erfahrungs-Labels const erfahrungLabel = { einsteiger: 'Einsteiger (erster Hund)', erfahren: 'Erfahrener Hundehalter', trainer: 'Trainer / Ausbilder', zuechter: 'Züchter', }; _container.innerHTML = `
${avatarInner}
${_esc(u.name)}
${_esc(u.email)}
${u.is_premium ? ` Ban Yaro Plus ` : ` Kostenlos `}
Mein Profil
${memberSince ? `
Mitglied seit ${_esc(memberSince)}
` : ''} ${u.bio ? `
${_esc(u.bio)}
` : ''} ${u.wohnort ? `
📍 ${_esc(u.wohnort)}
` : ''} ${u.erfahrung && erfahrungLabel[u.erfahrung] ? `
${_esc(erfahrungLabel[u.erfahrung])}
` : ''} ${u.social_link ? `
${_esc(u.social_link)}
` : ''} ${!u.bio && !u.wohnort && !u.erfahrung && !u.social_link ? `
Noch kein Profil ausgefüllt.
` : ''}
Aktivität
Lädt…
Trophäen
Lädt…
App-Einstellungen
Dark Mode
Erscheinungsbild der App
KI-Notiz-Assistent
Erkennt Muster in deinen Notizen und macht Vorschläge
${UI.icon('arrow-square-out')} App empfehlen
Lade Freunde ein — jede erfolgreiche Einladung wird in deinem Profil angezeigt.
Lade…
App installieren
Ban Yaro · banyaro.app
Deine Daten liegen auf einem eigenen Server in Deutschland.
v${typeof APP_VER !== 'undefined' ? APP_VER : '—'}
`; // Achievements laden (Streak + Stats + Badges) API.get('/achievements/me').then(a => { const statsEl = document.getElementById('settings-stats-body'); const badgesEl = document.getElementById('settings-badges-body'); if (!statsEl) return; const s = a.stats || {}, streak = a.streak || {}; const stat = (val, label) => `
${val}
${label}
`; statsEl.innerHTML = stat((s.total_km ?? 0) + ' km', 'gelaufen') + stat(s.routen ?? 0, 'Routen') + stat(s.pois ?? 0, 'POIs') + stat('#' + (a.rang ?? '–'), 'Rang'); const streakEl = document.getElementById('settings-streak'); if (streakEl) { const cur = streak.current || 0, mx = streak.max || 0; streakEl.innerHTML = cur > 0 ? `🔥 ${cur} Tage Streak ${mx > cur ? `Best: ${mx}` : ''}` : `🔥 Noch kein Streak — heute aktiv werden!`; } if (badgesEl && a.categories) { // SVG-Schild für jede Kategorie const shield = (color, dark, emoji, opacity = 1) => ` ${emoji} `; badgesEl.innerHTML = (a.categories || []).map(cat => { const cur = cat.current_tier; const nxt = cat.next_tier; const val = cat.current_value; // Alle Stufen als kleine Punkte const dots = (cat.alle_stufen || []).map(s => `
` ).join(''); // Aktuelles Schild const shieldSvg = cur ? shield(cur.color, cur.dark, cat.emoji) : shield('#9ca3af', '#6b7280', cat.emoji, 0.5); // Fortschrittsbalken const progressBar = nxt ? `
${val}${cat.einheit} / ${nxt.schwelle}${cat.einheit} → ${_esc(nxt.name)}
` : `
Höchste Stufe erreicht! 🎉
`; return `
${shieldSvg}
${_esc(cat.name)} ${cur ? `${_esc(cur.name)}` : ''}
${dots}
${progressBar}
`; }).join(''); } // Neue Badges als Toast if (a.new_badges?.length) { a.new_badges.forEach(b => { UI.toast.success(`${b.emoji} ${b.name} — ${b.tier} freigeschaltet!`); }); } }).catch(() => { const el = document.getElementById('settings-stats-body'); if (el) el.innerHTML = '
'; }); // Avatar-Hover-Overlay const avatarBtn = document.getElementById('settings-avatar-btn'); const avatarOverlay = avatarBtn?.querySelector('.avatar-overlay'); if (avatarBtn && avatarOverlay) { avatarBtn.addEventListener('mouseenter', () => { avatarOverlay.style.opacity = '1'; }); avatarBtn.addEventListener('mouseleave', () => { avatarOverlay.style.opacity = '0'; }); } // Avatar-Upload avatarBtn?.addEventListener('click', () => { document.getElementById('settings-avatar-input')?.click(); }); document.getElementById('settings-avatar-input')?.addEventListener('change', async e => { const file = e.target.files?.[0]; if (!file) return; try { const fd = new FormData(); fd.append('file', file); const res = await API.post('/profile/avatar', fd); _appState.user.avatar_url = res.avatar_url; UI.toast.success('Avatar aktualisiert.'); _render(); } catch { UI.toast.error('Avatar-Upload fehlgeschlagen.'); } }); // Profil bearbeiten document.getElementById('settings-profile-edit-btn')?.addEventListener('click', () => { const u = _appState.user; const inputStyle = `width:100%;box-sizing:border-box;padding:var(--space-2) var(--space-3); border:1.5px solid var(--c-border);border-radius:var(--radius-md); font-size:var(--text-sm);font-family:inherit; background:var(--c-surface);color:var(--c-text)`; const erfahrungOpts = [ ['', 'Bitte wählen...'], ['einsteiger', 'Einsteiger (erster Hund)'], ['erfahren', 'Erfahrener Hundehalter'], ['trainer', 'Trainer / Ausbilder'], ['zuechter', 'Züchter'], ].map(([val, label]) => `` ).join(''); const sichtbarkeitOpts = [ ['public', 'Öffentlich'], ['friends', 'Nur Freunde'], ['private', 'Privat'], ].map(([val, label]) => `` ).join(''); UI.modal.open({ title: 'Profil bearbeiten', body: `
`, footer: `
`, }); document.getElementById('profile-form')?.addEventListener('submit', async e => { e.preventDefault(); const btn = document.querySelector('[form="profile-form"]'); const fd = UI.formData(e.target); await UI.asyncButton(btn, async () => { const updated = await API.patch('/profile', { real_name: fd.real_name || '', bio: fd.bio || '', wohnort: fd.wohnort || '', erfahrung: fd.erfahrung || '', social_link: fd.social_link || '', profil_sichtbarkeit: fd.profil_sichtbarkeit || 'public', }); Object.assign(_appState.user, updated); UI.modal.close?.(); UI.toast.success('Profil gespeichert.'); _render(); }); }); }); document.getElementById('settings-check-update')?.addEventListener('click', async () => { const btn = document.getElementById('settings-check-update'); if (!('serviceWorker' in navigator)) { UI.toast.info('Service Worker nicht verfügbar.'); return; } if (btn) btn.textContent = 'Prüfe…'; try { const reg = await navigator.serviceWorker.getRegistration(); await reg?.update(); if (reg?.waiting) { // Neuer SW wartet — sofort aktivieren reg.waiting.postMessage({ type: 'SKIP_WAITING' }); UI.toast.success('Update wird installiert…'); } else { UI.toast.success('Ban Yaro ist aktuell (v' + (typeof APP_VER !== 'undefined' ? APP_VER : '—') + ').'); } } catch { UI.toast.error('Update-Prüfung fehlgeschlagen.'); } finally { if (btn) btn.innerHTML = UI.icon('arrows-clockwise') + ' Auf Update prüfen'; } }); document.getElementById('settings-logout-btn')?.addEventListener('click', async () => { const ok = await UI.modal.confirm({ title : 'Abmelden?', message: 'Du wirst aus deinem Konto abgemeldet.', confirmText: 'Abmelden', }); if (!ok) return; try { await API.auth.logout(); } catch { /* cookie wird trotzdem gelöscht */ } _appState.user = null; _appState.dogs = []; _appState.activeDog = null; UI.toast.info('Du wurdest abgemeldet.'); _render(); }); document.getElementById('settings-install-btn')?.addEventListener('click', () => { App.navigate('welcome', true, { install: true }); }); document.getElementById('settings-push-btn')?.addEventListener('click', async () => { try { await API.subscribeToPush(); UI.toast.success('Push-Benachrichtigungen aktiviert.'); } catch { UI.toast.warning('Push-Benachrichtigungen konnten nicht aktiviert werden.'); } }); document.getElementById('settings-calendar-btn')?.addEventListener('click', async () => { try { const { token } = await API.webcal.getToken(); const url = `webcal://${location.host}/api/webcal/${token}.ics`; const httpsUrl = `https://${location.host}/api/webcal/${token}.ics`; UI.modal.open({ title: `${UI.icon('calendar-dots')} Kalender abonnieren`, body: `

Abonniere deinen persönlichen Ban-Yaro-Kalender. Er enthält Impf-Erinnerungen, Läufigkeits-Termine, Events und Gassi-Treffen — immer aktuell.

${httpsUrl}
${UI.icon('calendar-dots')} In Kalender-App öffnen

Tipp: iOS → Einstellungen › Kalender › Accounts › Account hinzufügen › Andere › Kalenderabo

`, }); document.getElementById('cal-copy-btn')?.addEventListener('click', async () => { try { await navigator.clipboard.writeText(httpsUrl); UI.toast.success('URL kopiert.'); } catch { UI.toast.warning('Kopieren nicht möglich — URL oben manuell kopieren.'); } }); } catch(err) { console.error('Kalender-Fehler:', err); UI.toast.error('Kalender-Token konnte nicht geladen werden: ' + (err?.message || err)); } }); document.getElementById('select-theme')?.addEventListener('change', e => { const val = e.target.value; localStorage.setItem('by_theme', val); const html = document.documentElement; if (val === 'dark') { html.setAttribute('data-theme', 'dark'); } else if (val === 'light') { html.setAttribute('data-theme', 'light'); } else { html.removeAttribute('data-theme'); } UI.toast.info( val === 'dark' ? 'Dark Mode aktiviert.' : val === 'light' ? 'Hell-Modus aktiviert.' : 'Theme folgt der Systemeinstellung.' ); }); document.getElementById('toggle-pocket-mode')?.addEventListener('change', e => { localStorage.setItem('by_pocket_mode', String(e.target.checked)); UI.toast.info(e.target.checked ? 'Pocket-Modus aktiviert — Bildschirm bleibt bei Aufzeichnung an.' : 'Pocket-Modus deaktiviert.'); }); document.getElementById('toggle-notes-ki')?.addEventListener('change', async e => { const enabled = e.target.checked; const track = document.getElementById('toggle-notes-ki-track'); const thumb = document.getElementById('toggle-notes-ki-thumb'); if (track) track.style.background = enabled ? 'var(--c-primary)' : 'var(--c-border)'; if (thumb) thumb.style.left = enabled ? '22px' : '2px'; try { await API.patch('/profile', { notes_ki_enabled: enabled ? 1 : 0 }); _appState.user.notes_ki_enabled = enabled ? 1 : 0; UI.toast.success(enabled ? 'KI-Notiz-Assistent aktiviert.' : 'KI-Notiz-Assistent deaktiviert.'); } catch (err) { UI.toast.error(err?.message || 'Einstellung konnte nicht gespeichert werden.'); // Revert UI e.target.checked = !enabled; if (track) track.style.background = !enabled ? 'var(--c-primary)' : 'var(--c-border)'; if (thumb) thumb.style.left = !enabled ? '22px' : '2px'; } }); _loadReferral(); _loadBreederCard(); } // ---------------------------------------------------------- // KI-Toggle-Zeile (Hilfsfunktion für Züchter-Card) // ---------------------------------------------------------- function _kiToggleRow(key, label, user) { const active = user[key] !== 0; return `
${_esc(label)}
`; } // ---------------------------------------------------------- // ZÜCHTER-CARD — asynchron laden und in Slot rendern // ---------------------------------------------------------- async function _loadBreederCard() { const slot = document.getElementById('breeder-card-slot'); if (!slot) return; let status = null; try { status = await API.breeder.status(); } catch { // API nicht verfügbar — Card weglassen return; } const { rolle, breeder_status, profile } = status; const inputStyle = `width:100%;box-sizing:border-box;padding:var(--space-2) var(--space-3); border:1.5px solid var(--c-border);border-radius:var(--radius-md); font-size:var(--text-sm);font-family:inherit; background:var(--c-surface);color:var(--c-text)`; let statusBadge = ''; let actionBlock = ''; if (rolle === 'breeder' || rolle === 'admin') { statusBadge = ` ${UI.icon('check-circle')} ${rolle === 'admin' ? 'Admin — alle Züchter-Features verfügbar' : 'Verifizierter Züchter'} `; actionBlock = `
${profile?.zwingername ? `
Zwinger: ${_esc(profile.zwingername)}
` : ''} ${profile?.rasse_text ? `
Rasse: ${_esc(profile.rasse_text)}
` : ''}
${rolle === 'breeder' && profile ? ` ` : ''} ${rolle === 'admin' && !profile ? ` ` : ''} ${rolle === 'admin' && profile ? ` ` : ''} ${profile ? `
KI-Züchter-Assistenz
${_kiToggleRow('ki_zucht_wurfankuendigung', 'Wurfankündigungen schreiben', _appState.user || {})} ${_kiToggleRow('ki_zucht_genetik', 'Genetik-Erklärung für Käufer', _appState.user || {})} ${_kiToggleRow('ki_zucht_paarung', 'Paarungsanalyse', _appState.user || {})} ${_kiToggleRow('ki_zucht_beschreibung', 'Hunde-Beschreibungen', _appState.user || {})} ${_kiToggleRow('ki_zucht_jahresbericht', 'Jahresauswertung', _appState.user || {})}
${UI.icon('info')} Der Tierschutz-Check läuft immer automatisch und ist nicht abschaltbar.
` : ''}`; } else if (breeder_status === 'pending') { statusBadge = ` ${UI.icon('hourglass')} Antrag wird geprüft `; } else if (breeder_status === 'rejected') { statusBadge = ` ${UI.icon('x-circle')} Abgelehnt `; actionBlock = `
`; } else { actionBlock = `
`; } slot.innerHTML = `
Züchter-Profil
${statusBadge} ${actionBlock}
`; // Button-Handler binden const applyBtn = slot.querySelector('#breeder-apply-btn'); const reapplyBtn = slot.querySelector('#breeder-reapply-btn'); if (applyBtn || reapplyBtn) { (applyBtn || reapplyBtn).addEventListener('click', () => _openBreederApplyModal()); } slot.querySelector('#breeder-edit-profile-btn')?.addEventListener('click', () => _openBreederEditModal(profile) ); slot.querySelector('#breeder-admin-create-btn')?.addEventListener('click', async (e) => { const btn = e.currentTarget; btn.disabled = true; btn.textContent = 'Wird angelegt…'; try { await API.breeder.adminCreateProfile(); UI.toast.success('Admin-Züchterprofil angelegt. Bitte Seite neu laden.'); _loadBreederCard(); } catch (err) { UI.toast.error(err.message || 'Fehler beim Anlegen.'); btn.disabled = false; btn.innerHTML = `${UI.icon('plus')} Admin-Züchterprofil anlegen`; } }); // KI-Toggle-Handler slot.querySelectorAll('.ki-toggle-btn').forEach(btn => { btn.addEventListener('click', async () => { const key = btn.dataset.key; const active = btn.dataset.active === '1'; const newVal = active ? 0 : 1; // Optimistisches UI-Update btn.dataset.active = newVal ? '1' : '0'; btn.style.background = newVal ? 'var(--c-primary)' : 'var(--c-border)'; const thumb = btn.querySelector('.by-toggle-thumb'); if (thumb) thumb.style.left = newVal ? '22px' : '2px'; try { const updated = await API.patch('/profile', { [key]: newVal }); if (_appState?.user) _appState.user[key] = newVal; UI.toast.success(newVal ? 'KI-Feature aktiviert.' : 'KI-Feature deaktiviert.'); } catch (err) { // Revert btn.dataset.active = active ? '1' : '0'; btn.style.background = active ? 'var(--c-primary)' : 'var(--c-border)'; if (thumb) thumb.style.left = active ? '22px' : '2px'; UI.toast.error(err?.message || 'Einstellung konnte nicht gespeichert werden.'); } }); }); } // ---------------------------------------------------------- // ZÜCHTER-PROFIL BEARBEITEN MODAL // ---------------------------------------------------------- function _openBreederEditModal(profile) { const inputStyle = `width:100%;box-sizing:border-box;padding:var(--space-2) var(--space-3); border:1.5px solid var(--c-border);border-radius:var(--radius-md); font-size:var(--text-sm);font-family:inherit; background:var(--c-surface);color:var(--c-text)`; UI.modal.open({ title: `${UI.icon('pencil-simple')} Züchter-Profil bearbeiten`, body: `
`, footer: `
`, }); document.getElementById('breeder-edit-form')?.addEventListener('submit', async e => { e.preventDefault(); const btn = document.getElementById('breeder-edit-submit'); await UI.asyncButton(btn, async () => { const form = e.target; const data = { zwingername: form.zwingername.value.trim() || undefined, rasse_text: form.rasse_text.value.trim() || undefined, verein: form.verein.value.trim() || undefined, stadt: form.stadt.value.trim() || undefined, vdh_mitglied: form.vdh_mitglied.checked ? 1 : 0, website: form.website.value.trim() || undefined, beschreibung: form.beschreibung.value.trim() || undefined, }; await API.breeder.updateProfile(data); UI.modal.close?.(); UI.toast.success('Profil aktualisiert.'); _loadBreederCard(); }); }); } // ---------------------------------------------------------- // ZÜCHTER-ANTRAG MODAL // ---------------------------------------------------------- function _openBreederApplyModal() { const inputStyle = `width:100%;box-sizing:border-box;padding:var(--space-2) var(--space-3); border:1.5px solid var(--c-border);border-radius:var(--radius-md); font-size:var(--text-sm);font-family:inherit; background:var(--c-surface);color:var(--c-text)`; UI.modal.open({ title: `${UI.icon('certificate')} Züchter-Antrag stellen`, body: `
Zuchtbuch-Eintrag, Vereinsmitgliedschaft o.ä. (PDF, JPG, PNG, WebP)
`, footer: `
`, }); document.getElementById('breeder-apply-form')?.addEventListener('submit', async e => { e.preventDefault(); const btn = document.getElementById('breeder-apply-submit'); await UI.asyncButton(btn, async () => { const form = e.target; const fd = new FormData(form); // Checkbox-Wert normalisieren fd.set('vdh_mitglied', form.querySelector('[name="vdh_mitglied"]').checked ? '1' : '0'); await API.breeder.apply(fd); UI.modal.close?.(); UI.toast.success('Antrag eingereicht. Du wirst benachrichtigt sobald er geprüft wurde.'); // Card neu laden _loadBreederCard(); }); }); } // ---------------------------------------------------------- // REFERRAL — Einladungslink laden und rendern // ---------------------------------------------------------- async function _loadReferral() { const el = document.getElementById('referral-body'); if (!el) return; try { const r = await API.auth.referral(); el.innerHTML = `
${r.link}
${UI.icon('users')} ${r.count} ${r.count === 1 ? 'Person' : 'Personen'} über deinen Link registriert
${r.count > 0 ? `
${r.count >= 1 ? `${UI.icon('star')} Botschafter` : ''} ${r.count >= 5 ? `${UI.icon('star')} Super-Botschafter` : ''} ${r.count >= 10 ? `${UI.icon('star')} Top-Botschafter` : ''}
` : ''} `; document.getElementById('ref-share-btn')?.addEventListener('click', async () => { if (navigator.share) { navigator.share({ title: 'Ban Yaro — Die Hunde-App', text: 'Schau dir Ban Yaro an!', url: r.link }).catch(() => {}); } else { await navigator.clipboard.writeText(r.link); UI.toast.success('Link kopiert!'); } }); // QR-Code rendern (Bibliothek lazy laden) await App.loadScript('/js/qrcode.min.js'); new QRCode(document.getElementById('ref-qr'), { text: r.link, width: 160, height: 160, colorDark: '#000000', colorLight: '#ffffff', correctLevel: QRCode.CorrectLevel.H, }); } catch { el.innerHTML = ''; } } // ---------------------------------------------------------- // NICHT EINGELOGGT — Login / Registrierung // ---------------------------------------------------------- function _renderAuth(mode) { _mode = mode; _container.innerHTML = `
Ban Yaro

Ban Yaro

Alles rund um deinen Hund

${mode === 'login' ? _loginFormHTML() : _registerFormHTML()}
`; document.getElementById('tab-login') ?.addEventListener('click', () => _renderAuth('login')); document.getElementById('tab-register') ?.addEventListener('click', () => _renderAuth('register')); if (mode === 'login') { _bindLoginForm(); } else { _bindRegisterForm(); } } function _loginFormHTML() { return `
`; } function _registerFormHTML() { return `

Dieser Name ist öffentlich sichtbar und wird bei deinen Beiträgen, Pins und Kommentaren angezeigt.

🐾 Passwort-Vorschlag
Sichere Passphrase aus der Hundewelt — leicht zu merken, schwer zu knacken.

Mit der Registrierung stimmst du unseren Datenschutzhinweisen zu.
Deine Daten werden ausschließlich auf unserem Server gespeichert.

`; } function _bindPwToggle(inputId, btnId) { const input = document.getElementById(inputId); const btn = document.getElementById(btnId); if (!input || !btn) return; btn.addEventListener('click', () => { const visible = input.type === 'text'; input.type = visible ? 'password' : 'text'; btn.setAttribute('aria-label', visible ? 'Passwort anzeigen' : 'Passwort verbergen'); btn.querySelector('use').setAttribute('href', visible ? '/icons/phosphor.svg#eye' : '/icons/phosphor.svg#eye-slash'); }); } function _bindLoginForm() { _bindPwToggle('login-pw', 'login-pw-toggle'); document.getElementById('auth-form')?.addEventListener('submit', async e => { e.preventDefault(); const btn = e.target.querySelector('[type="submit"]'); const fd = UI.formData(e.target); await UI.asyncButton(btn, async () => { const result = await API.auth.login(fd.email, fd.password); localStorage.setItem('by_token', result.token); // User-Daten laden _appState.user = await API.auth.me(); document.getElementById('sidebar-username').textContent = _appState.user.name; // Hunde laden try { _appState.dogs = await API.dogs.list(); _appState.activeDog = _appState.dogs[0] || null; } catch { /* keine Hunde = okay */ } document.getElementById('header-login-btn')?.remove(); UI.toast.success(`Willkommen zurück, ${_appState.user.name}!`); // Push-Benachrichtigungen anbieten wenn noch nicht entschieden if (typeof Notification !== 'undefined' && Notification.permission === 'default') { _offerPushNotifications(); } // Nach Login: Welcome-Seite oder Profil anlegen if (_appState.activeDog) { App.navigate('welcome'); } else { App.navigate('dog-profile'); } }); }); } function _bindRegisterForm() { _bindPwToggle('register-pw', 'register-pw-toggle'); // Hundepassphrase-Generator initialisieren const phraseEl = document.getElementById('pw-gen-phrase'); const pwInput = document.getElementById('register-pw'); if (phraseEl) { const _refresh = () => { phraseEl.textContent = _genPassphrase(); }; _refresh(); document.getElementById('pw-gen-new')?.addEventListener('click', _refresh); document.getElementById('pw-gen-use')?.addEventListener('click', () => { const phrase = phraseEl.textContent; pwInput.value = phrase; pwInput.type = 'text'; // sichtbar machen document.getElementById('register-pw-toggle') ?.querySelector('use') ?.setAttribute('href', '/icons/phosphor.svg#eye-slash'); // kurzes visuelles Feedback const btn = document.getElementById('pw-gen-use'); btn.textContent = '✓ Übernommen'; btn.style.background = 'var(--c-success)'; setTimeout(() => { btn.textContent = 'Übernehmen'; btn.style.background = 'var(--c-primary)'; }, 1500); }); } document.getElementById('auth-form')?.addEventListener('submit', async e => { e.preventDefault(); const btn = e.target.querySelector('[type="submit"]'); const fd = UI.formData(e.target); if (!fd.name?.trim()) { UI.toast.warning('Bitte einen Namen eingeben.'); return; } if ((fd.password || '').length < 8) { UI.toast.warning('Passwort muss mindestens 8 Zeichen lang sein.'); return; } await UI.asyncButton(btn, async () => { const refCode = sessionStorage.getItem('by_ref_code') || ''; const result = await API.auth.register(fd.email, fd.password, fd.name.trim(), refCode || undefined); localStorage.setItem('by_token', result.token); if (refCode) sessionStorage.removeItem('by_ref_code'); _appState.user = await API.auth.me(); document.getElementById('sidebar-username').textContent = _appState.user.name; _appState.dogs = []; _appState.activeDog = null; document.getElementById('header-login-btn')?.remove(); UI.toast.success(`Willkommen bei Ban Yaro, ${_appState.user.name}!`); App.showOnboarding(); }); }); } // ---------------------------------------------------------- // PUSH-BENACHRICHTIGUNGEN ANBIETEN (nach Login) // ---------------------------------------------------------- function _offerPushNotifications() { // Kleiner Toast-Banner mit Ja-Button — nicht-invasiv const toastEl = document.createElement('div'); toastEl.id = 'push-offer-banner'; toastEl.style.cssText = [ 'position:fixed', 'bottom:calc(var(--nav-h, 64px) + var(--space-3))', 'left:50%', 'transform:translateX(-50%)', 'background:var(--c-surface)', 'border:1.5px solid var(--c-border)', 'border-radius:var(--radius-lg)', 'box-shadow:var(--shadow-lg)', 'padding:var(--space-3) var(--space-4)', 'display:flex', 'align-items:center', 'gap:var(--space-3)', 'font-size:var(--text-sm)', 'z-index:1100', 'max-width:340px', 'width:calc(100% - var(--space-8))', ].join(';'); toastEl.innerHTML = ` Push-Benachrichtigungen aktivieren? `; document.body.appendChild(toastEl); const remove = () => toastEl.remove(); document.getElementById('push-offer-yes')?.addEventListener('click', async () => { remove(); try { await API.subscribeToPush(); UI.toast.success('Push-Benachrichtigungen aktiviert.'); } catch { UI.toast.warning('Push-Benachrichtigungen konnten nicht aktiviert werden.'); } }); document.getElementById('push-offer-no')?.addEventListener('click', remove); // Automatisch ausblenden nach 12 Sekunden setTimeout(remove, 12000); } // ---------------------------------------------------------- // HELPER // ---------------------------------------------------------- function _esc(str) { if (!str) return ''; return str.replace(/&/g, '&').replace(//g, '>') .replace(/"/g, '"'); } // ---------------------------------------------------------- // PUBLIC // ---------------------------------------------------------- return { init, refresh }; })();