import { api } from "./lib/api.js"; import { getToken, setToken, clearToken, isLoggedIn } from "./lib/auth.js"; import { setStreakBadge } from "./lib/badge.js"; import { enqueue, flushQueue } from "./lib/offlineQueue.js"; import { registerPwa, wirePwaInstall, subscribePush } from "./lib/pwa.js"; import { renderSlots, renderHeatmap, updateStats, showToast, showConfetti, showEasterEgg } from "./lib/ui.js"; const STATUS = { upcoming: "Noch nicht", pending: "Jetzt!", overdue: "Überfällig", snoozed: "Snoozed", taken: "Genommen", missed: "Verpasst" }; function showScreen(id) { document.querySelectorAll(".screen").forEach((s) => { s.hidden = s.id !== id; }); } function wirePinForm() { const form = document.getElementById("pinForm"); const input = document.getElementById("pinInput"); const errEl = document.getElementById("loginError"); input.addEventListener("input", () => { const digits = input.value.replace(/\D/g, "").slice(0, 6); if (input.value !== digits) input.value = digits; errEl.hidden = true; }); form.addEventListener("submit", async (e) => { e.preventDefault(); const pin = input.value.trim(); if (pin.length < 4) return; errEl.hidden = true; try { const { token } = await api.login(pin); setToken(token); input.value = ""; await initApp(); } catch { errEl.textContent = "Falscher PIN. Dein Gehirn auch."; errEl.hidden = false; input.value = ""; input.focus(); } }); } function wireTabs() { document.querySelectorAll(".tab").forEach((tab) => { tab.addEventListener("click", () => { document.querySelectorAll(".tab").forEach((t) => t.classList.remove("active")); document.querySelectorAll(".tabPanel").forEach((p) => p.hidden = true); tab.classList.add("active"); const panel = document.getElementById(`tab-${tab.dataset.tab}`); panel.hidden = false; if (tab.dataset.tab === "history") loadHistory(); }); }); } async function handleTake(slotId) { const card = document.querySelector(`[data-slot="${slotId}"]`); try { if (!navigator.onLine) { await enqueue({ slot_id: slotId, status: "taken" }); showToast("Offline gespeichert — wird synchronisiert."); return; } const result = await api.log({ slot_id: slotId, status: "taken" }); card?.classList.add("dopamin"); showConfetti(); showToast(result.message); if (result.new_milestones?.length) { for (const m of result.new_milestones) { showEasterEgg(m.title, "Achievement freigeschaltet!"); } } await refreshDashboard(result.stats); } catch (e) { await enqueue({ slot_id: slotId, status: "taken" }); showToast("Gespeichert — Sync folgt."); } } async function handleSnooze(slotId, minutes) { try { const result = await api.snooze(slotId, minutes); showToast(result.message || `Snooze +${minutes} Min`); await refreshDashboard(); } catch (e) { showToast("Snooze fehlgeschlagen."); } } async function refreshDashboard(stats) { const [today, roast] = await Promise.all([ api.today(), api.roast().catch(() => ({ text: "Dein Gebrain wartet auf Koffein und Medis." })), ]); renderSlots(today.slots, handleTake, handleSnooze); document.getElementById("roastText").textContent = roast.text; const s = stats || await api.stats(); updateStats(s); await setStreakBadge(s.streak); } async function loadHistory() { const data = await api.history(90); renderHeatmap(data.days); updateStats(data.stats); const oracle = await api.oracle().catch(() => ({ text: "Das Orakel schweigt. Wahrscheinlich enttäuscht." })); document.getElementById("oracleText").textContent = oracle.text; } async function handleDeepLink() { const params = new URLSearchParams(location.search); const slot = params.get("slot"); if (params.get("action") === "take" && slot) { await handleTake(slot); history.replaceState({}, "", "/"); } else if (params.get("action") === "snooze" && slot) { const minutes = parseInt(params.get("minutes") || "15", 10); await handleSnooze(slot, minutes); history.replaceState({}, "", "/"); } } async function initApp() { showScreen("mainScreen"); wireTabs(); wirePwaInstall(); document.getElementById("logoutBtn").addEventListener("click", () => { clearToken(); location.reload(); }); document.getElementById("enablePush").addEventListener("click", async () => { const statusEl = document.getElementById("pushStatus"); try { const perm = await Notification.requestPermission(); if (perm !== "granted") { statusEl.textContent = "Permission verweigert."; return; } const { key } = await api.vapidKey(); if (!key) { statusEl.textContent = "VAPID keys nicht konfiguriert."; return; } const sub = await subscribePush(key); await api.pushSubscribe(sub); statusEl.textContent = "Push aktiv! Du wirst dezent erinnert."; } catch (e) { statusEl.textContent = e.message || "Push-Setup fehlgeschlagen."; } }); await flushQueue((data) => api.log(data)); await refreshDashboard(); await handleDeepLink(); } navigator.serviceWorker?.addEventListener("message", (event) => { if (event.data?.type === "SYNC_QUEUE") flushQueue((data) => api.log(data)); }); async function boot() { registerPwa(); if (isLoggedIn()) { try { await initApp(); return; } catch { clearToken(); } } showScreen("loginScreen"); wirePinForm(); } boot();