- 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
340 lines
13 KiB
JavaScript
340 lines
13 KiB
JavaScript
/* ============================================================
|
||
BAN YARO — Einstellungen / Account
|
||
Login, Registrierung, Logout, Account-Info.
|
||
============================================================ */
|
||
|
||
window.Page_settings = (() => {
|
||
|
||
let _container = null;
|
||
let _appState = null;
|
||
let _mode = 'login'; // 'login' | 'register'
|
||
|
||
// ----------------------------------------------------------
|
||
// 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
|
||
// ----------------------------------------------------------
|
||
function _renderAccount() {
|
||
const u = _appState.user;
|
||
_container.innerHTML = `
|
||
<div style="max-width:400px;margin:0 auto;padding:var(--space-4) 0">
|
||
|
||
<div class="card" style="padding:var(--space-5);margin-bottom:var(--space-4)">
|
||
<div style="display:flex;align-items:center;gap:var(--space-4)">
|
||
<div style="width:56px;height:56px;border-radius:50%;
|
||
background:var(--c-primary);color:#fff;
|
||
display:flex;align-items:center;justify-content:center;
|
||
font-size:1.5rem;font-weight:700;flex-shrink:0">
|
||
${_esc(u.name.charAt(0).toUpperCase())}
|
||
</div>
|
||
<div>
|
||
<div style="font-weight:700;font-size:var(--text-lg)">${_esc(u.name)}</div>
|
||
<div style="color:var(--c-text-secondary);font-size:var(--text-sm)">${_esc(u.email)}</div>
|
||
${u.is_premium
|
||
? `<span class="badge badge-primary" style="margin-top:var(--space-1)">
|
||
<svg class="ph-icon" aria-hidden="true" style="width:12px;height:12px"><use href="/icons/phosphor.svg#star"></use></svg> Ban Yaro Plus
|
||
</span>`
|
||
: `<span class="badge" style="margin-top:var(--space-1);
|
||
color:var(--c-text-secondary)">
|
||
Kostenlos
|
||
</span>`}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card" style="margin-bottom:var(--space-4)">
|
||
<div class="card-body" style="padding:0">
|
||
<div class="sidebar-item" data-page="dog-profile"
|
||
style="padding:var(--space-4);border-radius:0;border-bottom:1px solid var(--c-border)">
|
||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#dog"></use></svg>
|
||
<span>Hunde-Profile</span>
|
||
<span style="margin-left:auto;color:var(--c-text-secondary)">›</span>
|
||
</div>
|
||
<div class="sidebar-item" id="settings-push-btn"
|
||
style="padding:var(--space-4);border-radius:0;border-bottom:1px solid var(--c-border)">
|
||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#bell"></use></svg>
|
||
<span>Push-Benachrichtigungen</span>
|
||
<span style="margin-left:auto;color:var(--c-text-secondary)">›</span>
|
||
</div>
|
||
<div class="sidebar-item" id="settings-logout-btn"
|
||
style="padding:var(--space-4);border-radius:0;cursor:pointer;
|
||
color:var(--c-danger)">
|
||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#sign-out"></use></svg>
|
||
<span>Abmelden</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card" style="margin-bottom:var(--space-4)">
|
||
<div style="padding:var(--space-3) var(--space-4);
|
||
font-size:var(--text-xs);font-weight:600;
|
||
color:var(--c-text-secondary);text-transform:uppercase;
|
||
letter-spacing:0.05em;border-bottom:1px solid var(--c-border)">
|
||
App-Einstellungen
|
||
</div>
|
||
<div class="card-body" style="padding:0">
|
||
<div style="display:flex;align-items:center;gap:var(--space-3);
|
||
padding:var(--space-4);border-bottom:1px solid var(--c-border)">
|
||
<svg class="ph-icon" aria-hidden="true" style="width:1.25rem;height:1.25rem"><use href="/icons/phosphor.svg#eye-slash"></use></svg>
|
||
<div style="flex:1">
|
||
<div style="font-weight:500">Pocket-Modus beim Aufzeichnen</div>
|
||
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:2px">
|
||
Schwarzes Overlay hält den Bildschirm aktiv (GPS läuft) — ideal für die Hosentasche.
|
||
Helligkeit auf Minimum reduzieren für optimalen Akku-Schutz.
|
||
</div>
|
||
</div>
|
||
<label class="toggle" style="flex-shrink:0">
|
||
<input type="checkbox" id="toggle-pocket-mode"
|
||
${localStorage.getItem('by_pocket_mode') === 'true' ? 'checked' : ''}>
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="text-align:center;color:var(--c-text-secondary);
|
||
font-size:var(--text-xs)">
|
||
Ban Yaro · banyaro.app<br>
|
||
Deine Daten liegen auf einem eigenen Server in Deutschland.
|
||
</div>
|
||
|
||
</div>
|
||
`;
|
||
|
||
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-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('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.');
|
||
});
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// NICHT EINGELOGGT — Login / Registrierung
|
||
// ----------------------------------------------------------
|
||
function _renderAuth(mode) {
|
||
_mode = mode;
|
||
_container.innerHTML = `
|
||
<div style="max-width:380px;margin:0 auto;padding:var(--space-6) var(--space-4)">
|
||
|
||
<!-- Logo -->
|
||
<div style="text-align:center;margin-bottom:var(--space-6)">
|
||
<img src="/icons/icon-180.png" alt="Ban Yaro"
|
||
style="width:72px;height:72px;border-radius:var(--radius-lg);
|
||
margin-bottom:var(--space-3)">
|
||
<h1 style="font-size:var(--text-2xl);font-weight:700;margin:0">Ban Yaro</h1>
|
||
<p style="color:var(--c-text-secondary);margin:var(--space-1) 0 0">
|
||
Alles rund um deinen Hund
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Tab-Toggle -->
|
||
<div style="display:flex;background:var(--c-surface-2);
|
||
border-radius:var(--radius-md);padding:3px;
|
||
margin-bottom:var(--space-5)">
|
||
<button id="tab-login"
|
||
class="btn flex-1 ${mode === 'login' ? 'btn-primary' : 'btn-ghost'}"
|
||
style="border-radius:calc(var(--radius-md) - 2px)">
|
||
Anmelden
|
||
</button>
|
||
<button id="tab-register"
|
||
class="btn flex-1 ${mode === 'register' ? 'btn-primary' : 'btn-ghost'}"
|
||
style="border-radius:calc(var(--radius-md) - 2px)">
|
||
Registrieren
|
||
</button>
|
||
</div>
|
||
|
||
${mode === 'login' ? _loginFormHTML() : _registerFormHTML()}
|
||
|
||
</div>
|
||
`;
|
||
|
||
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 `
|
||
<form id="auth-form" autocomplete="on" novalidate>
|
||
<div class="form-group">
|
||
<label class="form-label">E-Mail</label>
|
||
<input class="form-control" type="email" name="email"
|
||
placeholder="deine@email.de" autocomplete="email" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Passwort</label>
|
||
<input class="form-control" type="password" name="password"
|
||
placeholder="Passwort" autocomplete="current-password" required>
|
||
</div>
|
||
<button type="submit" class="btn btn-primary w-full" style="margin-top:var(--space-2)">
|
||
Anmelden
|
||
</button>
|
||
</form>
|
||
`;
|
||
}
|
||
|
||
function _registerFormHTML() {
|
||
return `
|
||
<form id="auth-form" autocomplete="on" novalidate>
|
||
<div class="form-group">
|
||
<label class="form-label">Dein Name</label>
|
||
<input class="form-control" type="text" name="name"
|
||
placeholder="z. B. Maria" autocomplete="name" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">E-Mail</label>
|
||
<input class="form-control" type="email" name="email"
|
||
placeholder="deine@email.de" autocomplete="email" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Passwort</label>
|
||
<input class="form-control" type="password" name="password"
|
||
placeholder="Mindestens 8 Zeichen" autocomplete="new-password"
|
||
minlength="8" required>
|
||
</div>
|
||
<button type="submit" class="btn btn-primary w-full" style="margin-top:var(--space-2)">
|
||
Konto erstellen
|
||
</button>
|
||
<p style="text-align:center;font-size:var(--text-xs);
|
||
color:var(--c-text-secondary);margin-top:var(--space-3)">
|
||
Mit der Registrierung stimmst du unseren Datenschutzhinweisen zu.<br>
|
||
Deine Daten werden ausschließlich auf unserem Server gespeichert.
|
||
</p>
|
||
</form>
|
||
`;
|
||
}
|
||
|
||
function _bindLoginForm() {
|
||
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 */ }
|
||
|
||
UI.toast.success(`Willkommen zurück, ${_appState.user.name}!`);
|
||
|
||
// Nach Login: Tagebuch oder Profil anlegen
|
||
if (_appState.activeDog) {
|
||
App.navigate('diary');
|
||
} else {
|
||
App.navigate('dog-profile');
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
function _bindRegisterForm() {
|
||
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 result = await API.auth.register(fd.email, fd.password, fd.name.trim());
|
||
localStorage.setItem('by_token', result.token);
|
||
|
||
_appState.user = await API.auth.me();
|
||
document.getElementById('sidebar-username').textContent = _appState.user.name;
|
||
_appState.dogs = [];
|
||
_appState.activeDog = null;
|
||
|
||
UI.toast.success(`Willkommen bei Ban Yaro, ${_appState.user.name}! 🐕`);
|
||
// Direkt zur Profil-Anlage
|
||
App.navigate('dog-profile');
|
||
});
|
||
});
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// HELPER
|
||
// ----------------------------------------------------------
|
||
function _esc(str) {
|
||
if (!str) return '';
|
||
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||
.replace(/"/g, '"');
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// PUBLIC
|
||
// ----------------------------------------------------------
|
||
return { init, refresh };
|
||
|
||
})();
|