"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
import { helpContent, type HelpArticle, type HelpCategory, type HelpContent } from "./data";
import { Navbar, StatusBanner } from "./sections/header";
import { SearchHero } from "./sections/hero";
import { Categories, Faq, Guides, Popular, Stats } from "./sections/home";
import { Contact, type ContactPayload } from "./sections/contact";
import { ArticleView, CategoryView } from "./sections/article";
import { ChatWidget } from "./sections/chat";
import { Footer } from "./sections/footer";
import { scrollToId } from "./sections/ui";
export type { HelpContent, HelpArticle, HelpCategory, ContactPayload };
export interface HelpCenterTemplateProps {
/** Override brand, status, categories, articles, FAQ, bot copy or footer. Missing keys fall back to the demo content. */
content?: Partial<HelpContent>;
/** Called with the form values when the email form is submitted successfully. */
onContactSubmit?: (payload: ContactPayload) => void;
className?: string;
}
type View = { kind: "home" } | { kind: "category"; id: string } | { kind: "article"; id: string };
/**
* Lumen Help — a support center template.
* Status banner, sticky navbar, search hero with instant results, topic cards, popular articles,
* video guides, stats, contact options with an email form, FAQ and footer — plus category and
* article views (breadcrumbs, helpfulness vote) and a floating chat widget with canned bot replies.
*/
export function HelpCenterTemplate({ content, onContactSubmit, className }: HelpCenterTemplateProps) {
const c: HelpContent = { ...helpContent, ...content };
const [view, setView] = React.useState<View>({ kind: "home" });
const [chatOpen, setChatOpen] = React.useState(false);
const pending = React.useRef<string | null | undefined>(undefined);
const catMap = React.useMemo(() => new Map(c.categories.map((x) => [x.id, x])), [c.categories]);
const artMap = React.useMemo(() => new Map(c.articles.map((x) => [x.id, x])), [c.articles]);
const inCat = React.useCallback((id: string) => c.articles.filter((a) => a.category === id), [c.articles]);
React.useLayoutEffect(() => {
const target = pending.current;
if (target === undefined) return;
pending.current = undefined;
if (target) requestAnimationFrame(() => scrollToId(target));
else {
window.scrollTo({ top: 0, behavior: "auto" });
document.getElementById(view.kind === "article" ? "lh-article-title" : view.kind === "category" ? "lh-category-title" : "lh-hero-title")?.focus({ preventScroll: true });
}
}, [view]);
const go = (v: View, anchor?: string) => {
pending.current = anchor ?? null;
setView(v);
};
const goHome = (anchor?: string) => {
if (view.kind === "home") {
if (anchor) scrollToId(anchor);
else window.scrollTo({ top: 0, behavior: "smooth" });
return;
}
go({ kind: "home" }, anchor);
};
const openArticle = (id: string) => go({ kind: "article", id });
const openCategory = (id: string) => go({ kind: "category", id });
const contact = () => goHome("contact");
const article = view.kind === "article" ? artMap.get(view.id) : undefined;
const category = view.kind === "category" ? catMap.get(view.id) : article ? catMap.get(article.category) : undefined;
const footerNav = (label: string) => {
const map: Record<string, string> = { Topics: "topics", "Popular articles": "popular", "Video guides": "guides", "Contact support": "contact" };
if (map[label]) goHome(map[label]);
};
return (
<div className={cn("relative w-full bg-background text-foreground antialiased", className)}>
<a
href="#lh-main"
className="sr-only z-[90] rounded-md bg-foreground px-3 py-2 text-sm text-background focus:not-sr-only focus:fixed focus:left-3 focus:top-3"
>
Skip to content
</a>
<StatusBanner status={c.status} />
<Navbar brand={c.brand} links={c.nav} onNav={(h) => goHome(h.slice(1))} onHome={() => goHome()} onContact={contact} />
<main id="lh-main">
{article ? (
<ArticleView
key={article.id}
article={article}
category={category}
siblings={inCat(article.category)}
related={c.articles.filter((a) => a.id !== article.id && (a.category === article.category || a.tags.some((t) => article.tags.includes(t)))).slice(0, 4)}
onHome={() => goHome()}
onCategory={openCategory}
onOpen={openArticle}
onContact={contact}
onChat={() => setChatOpen(true)}
/>
) : category ? (
<CategoryView key={category.id} category={category} articles={inCat(category.id)} onHome={() => goHome("topics")} onOpen={openArticle} />
) : (
<>
<SearchHero hero={c.hero} articles={c.articles} categoryOf={(id) => catMap.get(id)} onOpen={openArticle} onContact={contact} />
<Categories categories={c.categories} countOf={(id) => inCat(id).length} topOf={(id) => inCat(id).slice(0, 2)} onCategory={openCategory} onOpen={openArticle} />
<Popular articles={c.popularIds.map((id) => artMap.get(id)).filter((a): a is HelpArticle => !!a)} categoryOf={(id) => catMap.get(id)} onOpen={openArticle} />
<Guides guides={c.guides} />
<Stats stats={c.stats} />
<Faq items={c.faq} />
<Contact content={c.contact} onChat={() => setChatOpen(true)} onSubmit={onContactSubmit} />
</>
)}
</main>
<Footer brand={c.brand} content={c.footer} onNav={footerNav} />
<ChatWidget open={chatOpen} onOpenChange={setChatOpen} bot={c.bot} articles={c.articles} onOpenArticle={openArticle} />
</div>
);
}
export default HelpCenterTemplate;