const TOKEN_KEY = "goldfish_token"; export function getToken() { return localStorage.getItem(TOKEN_KEY); } export function setToken(token) { localStorage.setItem(TOKEN_KEY, token); } export function clearToken() { localStorage.removeItem(TOKEN_KEY); } export function isLoggedIn() { return Boolean(getToken()); } async function request(path, options = {}) { const headers = { ...(options.headers || {}) }; if (options.body && !headers["Content-Type"]) { headers["Content-Type"] = "application/json"; } const token = getToken(); if (token) headers.Authorization = `Bearer ${token}`; const resp = await fetch(path, { ...options, headers }); let data = null; const ct = resp.headers.get("content-type") || ""; if (ct.includes("application/json")) { data = await resp.json(); } if (!resp.ok) { const detail = data?.detail; const msg = typeof detail === "string" ? detail : detail?.msg || resp.statusText; throw new Error(msg || "Request failed"); } return data; } export const api = { login(pin) { return request("/api/auth/pin", { method: "POST", body: JSON.stringify({ pin }) }); }, listRecaps(limit = 20, offset = 0) { return request(`/api/recaps?limit=${limit}&offset=${offset}`); }, getRecap(videoId) { return request(`/api/recaps/${encodeURIComponent(videoId)}`); }, createRecap(url, force = false) { return request("/api/recaps", { method: "POST", body: JSON.stringify({ url, force }) }); }, regenerateRecap(videoId) { return request(`/api/recaps/${encodeURIComponent(videoId)}/regenerate`, { method: "POST" }); }, }; export function formatTimestamp(seconds) { const s = Math.max(0, Math.floor(seconds)); const h = Math.floor(s / 3600); const m = Math.floor((s % 3600) / 60); const sec = s % 60; if (h > 0) return `${h}:${String(m).padStart(2, "0")}:${String(sec).padStart(2, "0")}`; return `${m}:${String(sec).padStart(2, "0")}`; } export function youtubeUrl(videoId, seconds) { const base = `https://www.youtube.com/watch?v=${videoId}`; return seconds != null ? `${base}&t=${Math.floor(seconds)}s` : base; } export const VERDICT_LABELS = { watch: { text: "Lohnt sich", emoji: "🎬", className: "verdict-watch" }, skim: { text: "Nur Stellen", emoji: "🐟", className: "verdict-skim" }, skip: { text: "Skip it", emoji: "💤", className: "verdict-skip" }, };