// Feedback modal — triggered by scroll-to-bottom after review loads. // Step-by-step: each question appears after the previous is answered. // Shares API_BASE and currentKey exposed by review.js on window. document.addEventListener("DOMContentLoaded", function () { const TOTAL_STEPS = 4; // step 5 is success screen const SHOWN_KEY = "pm_feedback_shown"; let currentStep = 1; const answers = {}; const overlay = document.getElementById("feedback-modal"); const closeBtn = document.getElementById("fb-modal-close"); const progressBar = document.getElementById("fb-progress-bar"); const stepNumEl = document.getElementById("fb-step-num"); const fbComments = document.getElementById("fb-comments"); const fbCharNum = document.getElementById("fb-char-num"); const fbError = document.getElementById("fb-error"); const skipBtn = document.getElementById("fb-skip-btn"); const submitBtn = document.getElementById("fb-submit-btn"); // ── Open / Close ─────────────────────────────────────────────────────────── function openModal() { overlay.classList.add("open"); showStep(1); } function closeModal() { overlay.classList.remove("open"); } closeBtn.addEventListener("click", closeModal); overlay.addEventListener("click", (e) => { if (e.target === overlay) closeModal(); }); document.addEventListener("keydown", (e) => { if (e.key === "Escape") closeModal(); }); // ── Progress bar ─────────────────────────────────────────────────────────── function updateProgress(step) { const pct = ((step - 1) / TOTAL_STEPS) * 100; progressBar.style.width = pct + "%"; stepNumEl.textContent = Math.min(step, TOTAL_STEPS); } // ── Step navigation ──────────────────────────────────────────────────────── const modalBox = overlay.querySelector(".fb-modal-box"); function showStep(n) { const steps = document.querySelectorAll(".fb-step"); steps.forEach((s) => s.classList.remove("active")); const next = document.querySelector(`.fb-step[data-step="${n}"]`); if (!next) return; next.classList.add("active"); currentStep = n; updateProgress(n); } function advanceStep() { showStep(currentStep + 1); } // ── Chip selection (steps 1-3) ───────────────────────────────────────────── document.querySelectorAll(".fb-chip").forEach((chip) => { chip.addEventListener("click", () => { const name = chip.dataset.name; const value = chip.dataset.value; // Highlight selected chip in group const group = chip.closest(".fb-chip-group"); group.querySelectorAll(".fb-chip").forEach((c) => c.classList.remove("selected")); chip.classList.add("selected"); answers[name] = value; // Auto-advance after short delay so user sees the selection setTimeout(() => advanceStep(), 340); }); }); // ── Step 4: comment ──────────────────────────────────────────────────────── if (fbComments) { fbComments.addEventListener("input", () => { fbCharNum.textContent = fbComments.value.length; }); } skipBtn?.addEventListener("click", () => submitFeedback("")); submitBtn?.addEventListener("click", () => submitFeedback(fbComments?.value.trim() || "")); // ── Submit ───────────────────────────────────────────────────────────────── async function submitFeedback(comment) { fbError.style.display = "none"; const key = window.currentKey; if (!key) { fbError.textContent = "Cannot identify the review. Please reload and try again."; fbError.style.display = "block"; return; } submitBtn.disabled = true; skipBtn.disabled = true; submitBtn.innerHTML = ' Submitting…'; try { const resp = await fetch(`${window.API_BASE}/api/feedback/${encodeURIComponent(key)}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ helpfulness: answers.helpfulness || null, has_critical_error: answers.has_critical_error === "true", has_suggestions: answers.has_suggestions === "true", comments: comment, }), }); if (!resp.ok) { const data = await resp.json().catch(() => ({})); fbError.textContent = data.detail || "Failed to submit. Please try again."; fbError.style.display = "block"; submitBtn.disabled = false; skipBtn.disabled = false; submitBtn.innerHTML = ' Submit'; return; } // Success step progressBar.style.width = "100%"; showStep(5); setTimeout(() => closeModal(), 2800); } catch { fbError.textContent = "Network error. Make sure the server is running."; fbError.style.display = "block"; submitBtn.disabled = false; skipBtn.disabled = false; submitBtn.innerHTML = ' Submit'; } } // ── Scroll trigger ───────────────────────────────────────────────────────── // Fire once when user reaches bottom, but only after review is rendered. // review.js sets reviewContent.style.display = "block" when done. let triggered = false; let reviewReady = false; // set to true by review.js after renderReview() function checkScrollTrigger() { if (triggered || !reviewReady) return; const scrolled = window.scrollY + window.innerHeight; const docHeight = document.documentElement.scrollHeight; if (scrolled >= docHeight - 200) { triggered = true; setTimeout(openModal, 2000); } } window.addEventListener("scroll", checkScrollTrigger, { passive: true }); // Called by review.js after renderReview() finishes — arms the trigger // and immediately checks in case user is already near the bottom window.onReviewReady = () => { reviewReady = true; triggered = false; // Short delay to let DOM settle after render setTimeout(checkScrollTrigger, 300); }; });