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."); } } const MOTIVATION_CACHE_KEY = "medis-motivation-day"; function showMotivationFromCache(day) { const el = document.getElementById("motivationText"); const cached = JSON.parse(sessionStorage.getItem(MOTIVATION_CACHE_KEY) || "null"); if (cached?.day === day && cached?.text) { el.textContent = cached.text; return true; } return false; } async function loadMotivation(day) { try { const motivation = await api.roast(); sessionStorage.setItem(MOTIVATION_CACHE_KEY, JSON.stringify({ day: motivation.day, text: motivation.text })); document.getElementById("motivationText").textContent = motivation.text; } catch { document.getElementById("motivationText").textContent = "Dein Gehirn ist nicht kaputt. Es wartet nur auf den richtigen Treiber."; } } async function refreshDashboard(stats) { const today = await api.today(); renderSlots(today.slots, handleTake, handleSnooze); if (!showMotivationFromCache(today.day)) { document.getElementById("motivationText").textContent = "…"; loadMotivation(today.day); } 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); } 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();