bff14167c9
This reverts commit 1cada42370.
190 lines
5.9 KiB
JavaScript
190 lines
5.9 KiB
JavaScript
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();
|