Best practice client portal

Legacy app

Latest milestone
Version 1.0 sitemap and annotated wireframes — January 13, 2025
Next milestone
Feedback and revision — Scheduling TBD
async function loadPosts() { const res = await fetch("posts.json", { cache: "no-store" }); if (!res.ok) throw new Error("Failed to load posts.json"); const posts = await res.json(); const published = (posts || []).filter(p => p && p.published !== false); const active = published[0]; const history = published.slice(1); // ACTIVE CARD const activeEl = document.getElementById("active-post"); if (!activeEl) return; if (!active) { activeEl.innerHTML = `
No published updates yet.
`; return; } const feedbackHtml = active.feedback_requested ? `
Feedback requested
` : ""; const ctaUrl = active.cta_url || ""; const ctaLabel = active.cta_label || "Check it out"; activeEl.innerHTML = `

${escapeHtml(active.title)}

${feedbackHtml}
${active.body_html || ""}
Posted ${escapeHtml(active.date_label || "")}
${ctaUrl ? `
${escapeHtml(ctaLabel)}
` : ""} `; // HISTORY const historyEl = document.getElementById("history-posts"); if (historyEl) { historyEl.innerHTML = history.map(p => { const dateLabel = escapeHtml(p.date_label || ""); const title = escapeHtml(p.title || ""); const excerpt = escapeHtml(p.excerpt || ""); const link = p.cta_url ? ` ${escapeHtml(p.cta_label || "Check it out")} ` : ""; return `
${dateLabel}
${title}

${excerpt}

${link}
`; }).join("\n"); } // Re-render Lucide icons (we injected new nodes) if (window.lucide && typeof lucide.createIcons === "function") { lucide.createIcons({ attrs: { class: "lucide", "stroke-width": 1.5 } }); } // Bind tilt to newly injected active CTA button bindTiltToActiveButton(); } loadPosts().catch(err => { console.error(err); const activeEl = document.getElementById("active-post"); if (activeEl) activeEl.innerHTML = `
Unable to load updates.
`; });