From 5acfa9d8f61437a6b08f2865d8b1e431b6ba744a Mon Sep 17 00:00:00 2001 From: rene Date: Sat, 18 Apr 2026 18:41:12 +0200 Subject: [PATCH] =?UTF-8?q?Feature:=20Onboarding-Wizard=20f=C3=BCr=20neue?= =?UTF-8?q?=20User=20(3=20Schritte)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neues Seiten-Modul window.Page_onboarding mit Schritt-Indikator, Willkommenstext, Hund-Anlegen-Formular (Name/Rasse/Geburtstag/Foto) und Fertig-Screen mit Tagebuch-CTA. Wird nach Login bei dogs.length===0 angezeigt; localStorage-Flag by_onboarding_done verhindert Wiederholung. --- backend/static/js/pages/onboarding.js | 461 ++++++++++++++++++++++++++ 1 file changed, 461 insertions(+) create mode 100644 backend/static/js/pages/onboarding.js diff --git a/backend/static/js/pages/onboarding.js b/backend/static/js/pages/onboarding.js new file mode 100644 index 0000000..6cfda8b --- /dev/null +++ b/backend/static/js/pages/onboarding.js @@ -0,0 +1,461 @@ +/* ============================================================ + BAN YARO — Onboarding-Wizard + 3-Schritt-Wizard für neue User ohne Hund. + ============================================================ */ + +window.Page_onboarding = (() => { + + let _container = null; + let _appState = null; + let _step = 1; // 1 = Willkommen, 2 = Hund anlegen, 3 = Fertig + + // ---------------------------------------------------------- + // INIT + // ---------------------------------------------------------- + async function init(container, appState) { + _container = container; + _appState = appState; + _step = 1; + _render(); + } + + function refresh() { + // Wenn User nach Abschluss zurücknavigiert und schon fertig ist → Tagebuch + if (localStorage.getItem('by_onboarding_done')) { + App.navigate('diary'); + return; + } + _render(); + } + + function onDogChange() {} + + // ---------------------------------------------------------- + // RENDER + // ---------------------------------------------------------- + function _render() { + _container.innerHTML = ` +
+ + +
+ ${[1, 2, 3].map(n => ` +
+
+ ${n < _step + ? `` + : n} +
+ ${n < 3 ? `
` : ''} +
+ `).join('')} +
+ + +
+ ${_stepContent()} +
+ +
+ `; + + _bindEvents(); + } + + function _stepContent() { + if (_step === 1) return _step1(); + if (_step === 2) return _step2(); + if (_step === 3) return _step3(); + return ''; + } + + // ---------------------------------------------------------- + // SCHRITT 1 — Willkommen + // ---------------------------------------------------------- + function _step1() { + return ` +
+ + +
+ Ban Yaro +
+ + +

+ Willkommen bei Ban Yaro! +

+ + +

+ Ban Yaro ist dein digitaler Begleiter für alles rund um deinen Hund — + Tagebuch, Gesundheit, Karte und Community in einer App. +

+

+ In nur zwei Schritten richtest du dein Profil ein und bist sofort startklar. +

+ + +
+ ${[ + ['book-open', 'Tagebuch', 'Momente & Fotos'], + ['syringe', 'Gesundheit', 'Impfungen & Arzt'], + ['map-trifold', 'Karte', 'Orte & Routen'], + ['users', 'Community', 'Freunde & Treffen'], + ].map(([icon, title, desc]) => ` +
+
+ +
+
+
${title}
+
${desc}
+
+
+ `).join('')} +
+ + +
+ + +
+ +
+ `; + } + + // ---------------------------------------------------------- + // SCHRITT 2 — Hund anlegen + // ---------------------------------------------------------- + function _step2() { + const today = new Date().toISOString().slice(0, 10); + return ` +
+ + +
+
+ +
+

+ Dein erster Hund +

+

+ Nur der Name ist Pflicht — alles andere kannst du später ergänzen. +

+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + + +
+ +
+ + +
+ + +
+
+ +
+ +
+ `; + } + + // ---------------------------------------------------------- + // SCHRITT 3 — Fertig + // ---------------------------------------------------------- + function _step3() { + const dogName = _appState.activeDog?.name; + return ` +
+ + +
+
+ +
+
+ + +

+ Dein Profil ist bereit! +

+ ${dogName ? ` +

+ ${_esc(dogName)} ist jetzt in Ban Yaro. + Du kannst jetzt Einträge im Tagebuch anlegen, die Gesundheit pflegen + und viele weitere Funktionen nutzen. +

+ ` : ` +

+ Ban Yaro ist bereit. Du kannst jetzt die Karte, das Wiki und viele + weitere Funktionen erkunden. +

+ `} +

+ Du kannst dein Hundeprofil jederzeit unter + Mein Hund bearbeiten und ergänzen. +

+ + +
+ + ${dogName ? ` + + ` : ''} +
+ +
+ `; + } + + // ---------------------------------------------------------- + // EVENTS + // ---------------------------------------------------------- + function _bindEvents() { + // Weiter-Button (Schritt 1) + _container.querySelector('#ob-next-btn')?.addEventListener('click', () => { + _step = 2; + _render(); + }); + + // Zurück-Button (Schritt 2) + _container.querySelector('#ob-back-btn')?.addEventListener('click', () => { + _step = 1; + _render(); + }); + + // Überspringen + _container.querySelector('#ob-skip-btn')?.addEventListener('click', () => { + _finish(); + }); + + // Foto-Vorschau + _container.querySelector('#ob-photo-input')?.addEventListener('change', e => { + const file = e.target.files?.[0]; + if (!file) return; + const url = URL.createObjectURL(file); + const preview = _container.querySelector('#ob-photo-preview'); + const img = _container.querySelector('#ob-photo-img'); + const label = _container.querySelector('#ob-photo-label'); + if (preview) preview.style.display = ''; + if (img) img.src = url; + if (label) label.textContent = file.name.length > 20 + ? file.name.slice(0, 17) + '...' + : file.name; + }); + + // Formular abschicken (Schritt 2) + _container.querySelector('#ob-dog-form')?.addEventListener('submit', async e => { + e.preventDefault(); + await _saveDog(e.target); + }); + + // Zu Tagebuch (Schritt 3) + _container.querySelector('#ob-diary-btn')?.addEventListener('click', () => { + App.navigate('diary'); + }); + + // Zu Hund-Profil (Schritt 3) + _container.querySelector('#ob-profile-btn')?.addEventListener('click', () => { + App.navigate('dog-profile'); + }); + } + + // ---------------------------------------------------------- + // HUND SPEICHERN + // ---------------------------------------------------------- + async function _saveDog(form) { + const saveBtn = _container.querySelector('#ob-save-btn'); + if (saveBtn) { + saveBtn.disabled = true; + saveBtn.innerHTML = ` + + Wird angelegt… + `; + } + + try { + const data = new FormData(form); + const payload = { + name: data.get('name')?.trim(), + rasse: data.get('rasse')?.trim() || null, + geburtstag: data.get('geburtstag') || null, + }; + + if (!payload.name) { + UI.toast.error('Bitte gib einen Namen ein.'); + return; + } + + // Hund anlegen + const dog = await API.dogs.create(payload); + + // Foto hochladen (falls vorhanden) + const fotoFile = data.get('foto'); + if (fotoFile && fotoFile.size > 0) { + try { + const fd = new FormData(); + fd.append('file', fotoFile); + await API.dogs.uploadPhoto(dog.id, fd); + } catch { + // Foto-Upload-Fehler ist nicht kritisch + UI.toast.warning('Hund angelegt, Foto konnte nicht hochgeladen werden.'); + } + } + + // State aktualisieren + const dogs = await API.dogs.list(); + _appState.dogs = dogs; + const newDog = dogs.find(d => d.id === dog.id) || dogs[0]; + _appState.activeDog = newDog; + if (newDog) { + localStorage.setItem('by_active_dog', String(newDog.id)); + } + App.renderDogSwitcher(); + + UI.toast.success(`${_esc(dog.name)} wurde angelegt!`); + + _step = 3; + _render(); + + } catch (err) { + UI.toast.error(err.message || 'Hund konnte nicht angelegt werden.'); + } finally { + if (saveBtn) { + saveBtn.disabled = false; + saveBtn.innerHTML = ` + + Hund anlegen + `; + } + } + } + + // ---------------------------------------------------------- + // ABSCHLUSS + // ---------------------------------------------------------- + function _finish() { + localStorage.setItem('by_onboarding_done', '1'); + if (_appState.dogs.length > 0) { + App.navigate('diary'); + } else { + App.navigate('map'); + } + } + + // ---------------------------------------------------------- + // HELPER + // ---------------------------------------------------------- + function _esc(s) { + return UI.escape(s || ''); + } + + // ---------------------------------------------------------- + // PUBLIC + // ---------------------------------------------------------- + return { init, refresh, onDogChange }; + +})();