const API = window.location.origin; function getToken() { return localStorage.getItem("joblin_token"); } function apiHeaders() { const h = { "Content-Type": "application/json" }; const t = getToken(); if (t) h["Authorization"] = `Bearer ${t}`; return h; } async function apiFetch(method, path, body) { const opts = { method, headers: apiHeaders() }; if (body) opts.body = JSON.stringify(body); const r = await fetch(API + path, opts); if (r.status === 401 && !path.startsWith("/api/auth/login")) { localStorage.removeItem("joblin_token"); localStorage.removeItem("joblin_user"); window.location.href = "/login.html"; throw new Error("Unauthorized"); } const text = await r.text(); if (!text && !r.ok) throw new Error("Server error (empty response)"); let data; try { data = JSON.parse(text); } catch (e) { throw new Error((r.status === 500 ? "Server error: " : "Request failed: ") + text.slice(0, 200)); } if (!r.ok) { let msg = "Request failed"; if (typeof data.detail === "string") msg = data.detail; else if (Array.isArray(data.detail)) msg = data.detail.map(e => e.msg || e).join("; "); throw new Error(msg); } return data; } function showToast(msg, type = "success") { let t = document.getElementById("toast"); if (!t) { t = document.createElement("div"); t.id = "toast"; t.className = "toast"; document.body.appendChild(t); } t.className = `toast ${type} show`; t.textContent = msg; clearTimeout(t._timeout); t._timeout = setTimeout(() => t.classList.remove("show"), 3000); } function getCurrentUser() { const u = localStorage.getItem("joblin_user"); return u ? JSON.parse(u) : null; } function setCurrentUser(user) { localStorage.setItem("joblin_user", JSON.stringify(user)); } function updateHeader() { const user = getCurrentUser(); const nameEl = document.getElementById("user-name"); const emailEl = document.getElementById("user-email"); const avatarEl = document.getElementById("avatarInitial"); if (nameEl && user) nameEl.textContent = user.name || user.email; if (emailEl && user) emailEl.textContent = user.email || ""; if (avatarEl && user) { const initial = (user.name || user.email || "U")[0].toUpperCase(); avatarEl.textContent = initial; } const path = window.location.pathname.split("/").pop() || "dashboard.html"; document.querySelectorAll(".nav-item").forEach(a => { a.classList.toggle("active", a.getAttribute("href") === path); }); const isAdminUser = user && (user.is_admin === true || user.is_admin === 1); const adminSections = document.querySelectorAll(".admin-only"); const showAdmin = isAdminUser && adminMode(); adminSections.forEach(el => { el.style.display = showAdmin ? "" : "none"; }); const toggleEl = document.getElementById("adminToggle"); const toggleLabel = document.getElementById("adminToggleLabel"); if (toggleEl && isAdminUser) { toggleEl.style.display = ""; if (toggleLabel) toggleLabel.textContent = adminMode() ? "User View" : "Admin View"; } else if (toggleEl) { toggleEl.style.display = "none"; } } function isAdmin() { const u = getCurrentUser(); return u && (u.is_admin === true || u.is_admin === 1); } function adminMode() { return localStorage.getItem("admin_mode") === "true"; } function toggleAdminMode() { const newVal = adminMode() ? "false" : "true"; localStorage.setItem("admin_mode", newVal); location.reload(); } function togglePwd(id, el) { const inp = document.getElementById(id); const isPwd = inp.type === "password"; inp.type = isPwd ? "text" : "password"; el.innerHTML = isPwd ? "👀" : "👁"; } function logout() { localStorage.removeItem("joblin_token"); localStorage.removeItem("joblin_user"); localStorage.removeItem("admin_mode"); window.location.href = "/login.html"; } function escHtml(s) { if (!s) return ""; return s.replace(/&/g,"&").replace(//g,">").replace(/"/g,"""); } function renderQualityScores(scores) { if (!scores || !Object.keys(scores).length) return ""; const labels = { achievement_density: "Achievement Density", bullet_depth: "Bullet Depth", skills: "Skills", summary: "Summary", education: "Education", cover_letter: "Cover Letter", }; return `