nxtgauge-frontend-solid/src/routes/help-center/article/[slug].tsx
Ashwin Kumar 7671ad8e55 feat: improve Help Center UI with mixed dark/light theme and structured content
- Updated Help Center with dark hero and light content sections
- Added ArticleContent component for rendering structured content blocks
- Updated seed data with detailed articles matching admin KB categories
- Fixed article alignment and spacing issues
- Uses ContentBlock[] instead of HTML strings for type-safe content
2026-04-09 21:52:16 +02:00

565 lines
22 KiB
TypeScript

import { A, useParams } from "@solidjs/router";
import { Meta, Title } from "@solidjs/meta";
import { Show, For, createSignal, createResource, onCleanup, onMount, createMemo } from "solid-js";
import { fetchArticleBySlug, fetchRelatedArticles } from "~/lib/help-center";
import PublicBackground from "~/components/PublicBackground";
import PublicHeader from "~/components/PublicHeader";
import PublicFooter from "~/components/PublicFooter";
import ArticleContent from "~/components/ArticleContent";
function categoryTitle(input: string) {
return input
.split("-")
.filter(Boolean)
.map((chunk) => chunk[0].toUpperCase() + chunk.slice(1))
.join(" ");
}
export default function HelpCenterArticlePage() {
const params = useParams();
const slug = createMemo(() => String(params.slug || "").trim());
const [scrollY, setScrollY] = createSignal(0);
const [article] = createResource(() => params.slug, fetchArticleBySlug);
const [relatedArticles] = createResource(
() => article(),
(item) => (item ? fetchRelatedArticles({ article: item, limit: 4 }) : [])
);
const canonical = createMemo(
() => `https://test121.nxtgauge.com/help-center/article/${encodeURIComponent(slug())}`
);
const pageTitle = createMemo(() => {
const a = article();
return a ? `${a.title} | Nxtgauge Help Center` : "Help Center Article | Nxtgauge";
});
const pageDescription = createMemo(() => {
const a = article();
return a?.summary || "Read support and product guidance from Nxtgauge Help Center.";
});
onMount(() => {
const onScroll = () => setScrollY(window.scrollY || 0);
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
onCleanup(() => window.removeEventListener("scroll", onScroll));
});
const formattedDate = createMemo(() => {
const a = article();
if (!a?.updatedAt) return "";
const date = new Date(a.updatedAt);
return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
});
return (
<main class="lp-main">
<Title>{pageTitle()}</Title>
<Meta name="description" content={pageDescription()} />
<Meta property="og:title" content={pageTitle()} />
<Meta property="og:description" content={pageDescription()} />
<Meta property="og:type" content="article" />
<Meta property="og:url" content={canonical()} />
<Meta name="twitter:card" content="summary_large_image" />
<Meta name="twitter:title" content={pageTitle()} />
<Meta name="twitter:description" content={pageDescription()} />
<link rel="canonical" href={canonical()} />
<PublicBackground scrollY={scrollY()} />
<div class="lp-content">
<PublicHeader />
{/* Breadcrumb - Dark */}
<section style={{ padding: "20px 0 0" }}>
<div class="container" style={{ "max-width": "960px" }}>
<nav
style={{
display: "flex",
"align-items": "center",
gap: "8px",
"font-size": "14px",
color: "rgba(255,255,255,0.5)",
}}
>
<A
href="/help-center"
style={{
color: "rgba(255,255,255,0.6)",
"text-decoration": "none",
display: "flex",
"align-items": "center",
gap: "6px",
}}
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<polyline points="9,22 9,12 15,12 15,22" />
</svg>
Help Center
</A>
<span style={{ color: "rgba(255,255,255,0.3)" }}>/</span>
<Show when={article()}>
{(a) => (
<span style={{ color: "#fd6116", "font-weight": "600" }}>
{a().category || categoryTitle(a().categoryKey)}
</span>
)}
</Show>
</nav>
</div>
</section>
{/* Loading State */}
<Show when={article.loading}>
<section class="public-section scene-dark">
<div class="container" style={{ "max-width": "960px" }}>
<div
class="panel panel-light"
style={{ padding: "60px 40px", "text-align": "center" }}
>
<div
class="spinner"
style={{
width: "40px",
height: "40px",
border: "3px solid rgba(253,97,22,0.2)",
"border-top-color": "#fd6116",
"border-radius": "50%",
animation: "spin 1s linear infinite",
margin: "0 auto 20px",
}}
/>
<p style={{ color: "#6B7280", margin: 0 }}>Loading article...</p>
</div>
</div>
</section>
</Show>
{/* Not Found State */}
<Show when={!article.loading && !article()}>
<section class="public-section scene-dark">
<div class="container" style={{ "max-width": "960px" }}>
<div
class="panel panel-light"
style={{ padding: "60px 40px", "text-align": "center" }}
>
<div
style={{
width: "64px",
height: "64px",
background: "rgba(253,97,22,0.1)",
"border-radius": "16px",
display: "flex",
"align-items": "center",
"justify-content": "center",
margin: "0 auto 24px",
}}
>
<svg
width="32"
height="32"
viewBox="0 0 24 24"
fill="none"
stroke="#fd6116"
stroke-width="2"
>
<circle cx="12" cy="12" r="10" />
<line x1="12" y1="8" x2="12" y2="12" />
<line x1="12" y1="16" x2="12.01" y2="16" />
</svg>
</div>
<h1
style={{
margin: "0 0 12px",
color: "#111827",
"font-size": "28px",
"font-weight": "700",
}}
>
Article not found
</h1>
<p style={{ margin: "0 0 24px", color: "#6B7280", "font-size": "16px" }}>
The requested Help Center article is unavailable or has been moved.
</p>
<A class="btn primary" href="/help-center">
Back to Help Center
</A>
</div>
</div>
</section>
</Show>
{/* Article Content */}
<Show when={!article.loading && article()}>
{(a) => (
<>
{/* Dark Article Header */}
<section class="public-section scene-dark" style={{ padding: "20px 0 30px" }}>
<div class="container" style={{ "max-width": "960px" }}>
<div style={{ padding: "0 0 20px" }}>
<div
style={{
display: "inline-flex",
"align-items": "center",
gap: "6px",
background: "rgba(253,97,22,0.15)",
color: "#fd6116",
padding: "6px 14px",
"border-radius": "20px",
"font-size": "12px",
"font-weight": "700",
"text-transform": "uppercase",
"letter-spacing": "0.5px",
}}
>
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.5"
>
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
</svg>
{a().category || categoryTitle(a().categoryKey)}
</div>
<h1
style={{
margin: "16px 0 12px",
color: "#fff",
"font-size": "36px",
"font-weight": "800",
"line-height": "1.2",
}}
>
{a().title}
</h1>
<p
style={{
margin: 0,
color: "rgba(255,255,255,0.7)",
"font-size": "18px",
"line-height": "1.6",
}}
>
{a().summary}
</p>
<div
style={{
display: "flex",
"align-items": "center",
gap: "12px",
"margin-top": "20px",
"flex-wrap": "wrap",
}}
>
<Show when={a().tags?.length > 0}>
<div style={{ display: "flex", gap: "8px", "flex-wrap": "wrap" }}>
<For each={a().tags}>
{(tag) => (
<span
style={{
display: "inline-flex",
"align-items": "center",
background: "rgba(255,255,255,0.1)",
color: "rgba(255,255,255,0.8)",
padding: "4px 10px",
"border-radius": "6px",
"font-size": "12px",
border: "1px solid rgba(255,255,255,0.15)",
}}
>
{tag}
</span>
)}
</For>
</div>
</Show>
<div
style={{
display: "flex",
"align-items": "center",
gap: "6px",
color: "rgba(255,255,255,0.5)",
"font-size": "13px",
}}
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="12" cy="12" r="10" />
<polyline points="12,6 12,12 16,14" />
</svg>
Updated {formattedDate()}
</div>
</div>
</div>
</div>
</section>
{/* Light Content Section */}
<section style={{ background: "#F8FAFC", padding: "40px 0 30px" }}>
<div class="container" style={{ "max-width": "960px" }}>
<div
style={{
background: "#fff",
"border-radius": "20px",
border: "1px solid #E5E7EB",
padding: "40px",
"box-shadow": "0 1px 3px rgba(0,0,0,0.05)",
}}
>
<Show
when={a().content}
fallback={<p style={{ color: "#6B7280" }}>No content available.</p>}
>
<ArticleContent blocks={a().content} />
</Show>
<div
style={{
display: "flex",
gap: "12px",
"margin-top": "40px",
"padding-top": "32px",
"border-top": "1px solid #E5E7EB",
}}
>
<A
class="btn"
href="/help-center"
style={{ display: "inline-flex", "align-items": "center", gap: "6px" }}
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="19" y1="12" x2="5" y2="12" />
<polyline points="12,19 5,12 12,5" />
</svg>
Back to Help Center
</A>
<A class="btn primary" href="/contact">
Get Started
</A>
</div>
</div>
</div>
</section>
{/* Related Articles - Light */}
<Show when={(relatedArticles() || []).length > 0}>
<section style={{ background: "#F8FAFC", padding: "20px 0 40px" }}>
<div class="container" style={{ "max-width": "960px" }}>
<div
style={{
background: "#fff",
"border-radius": "20px",
padding: "32px",
border: "1px solid #E5E7EB",
"box-shadow": "0 1px 3px rgba(0,0,0,0.05)",
}}
>
<h2
style={{
margin: "0 0 20px",
color: "#111827",
"font-size": "20px",
"font-weight": "700",
}}
>
Related Articles
</h2>
<div
style={{
display: "grid",
"grid-template-columns": "repeat(auto-fit, minmax(260px, 1fr))",
gap: "16px",
}}
>
<For each={relatedArticles() || []}>
{(related) => (
<A
href={`/help-center/article/${related.slug}`}
style={{ "text-decoration": "none" }}
>
<article
style={{
display: "flex",
"flex-direction": "column",
gap: "8px",
background: "#F9FAFB",
border: "1px solid #E5E7EB",
"border-radius": "12px",
padding: "20px",
transition: "all 0.2s",
cursor: "pointer",
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = "#fff";
e.currentTarget.style.borderColor = "#fd6116";
e.currentTarget.style.boxShadow =
"0 4px 12px rgba(253,97,22,0.1)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = "#F9FAFB";
e.currentTarget.style.borderColor = "#E5E7EB";
e.currentTarget.style.boxShadow = "none";
}}
>
<span
style={{
color: "#fd6116",
"font-size": "11px",
"font-weight": "700",
"text-transform": "uppercase",
"letter-spacing": "0.5px",
}}
>
{related.category || "General"}
</span>
<h3
style={{
margin: 0,
color: "#111827",
"font-size": "15px",
"font-weight": "600",
"line-height": "1.4",
}}
>
{related.title}
</h3>
<p
style={{
margin: 0,
color: "#6B7280",
"font-size": "13px",
"line-height": "1.5",
}}
>
{related.summary}
</p>
</article>
</A>
)}
</For>
</div>
</div>
</div>
</section>
</Show>
{/* Help Section - Dark */}
<section style={{ padding: "0 0 40px" }}>
<div class="container" style={{ "max-width": "960px" }}>
<div
style={{
background:
"linear-gradient(135deg, rgba(253,97,22,0.15) 0%, rgba(253,97,22,0.08) 100%)",
"border-radius": "20px",
padding: "40px",
border: "1px solid rgba(253,97,22,0.25)",
"text-align": "center",
}}
>
<div
style={{
width: "56px",
height: "56px",
background: "rgba(253,97,22,0.2)",
"border-radius": "14px",
display: "flex",
"align-items": "center",
"justify-content": "center",
margin: "0 auto 20px",
}}
>
<svg
width="28"
height="28"
viewBox="0 0 24 24"
fill="none"
stroke="#fd6116"
stroke-width="2"
>
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
</svg>
</div>
<h2
style={{
margin: "0 0 12px",
color: "#fff",
"font-size": "24px",
"font-weight": "700",
}}
>
Need more help?
</h2>
<p
style={{
margin: "0 0 24px",
color: "rgba(255,255,255,0.7)",
"font-size": "16px",
}}
>
If this article does not solve your issue, send your question with context to
support.
</p>
<div
style={{
display: "flex",
gap: "12px",
"justify-content": "center",
"flex-wrap": "wrap",
}}
>
<a
class="btn primary"
href="mailto:support@nxtgauge.com?subject=Nxtgauge%20Help%20Center%20Question"
style={{ display: "inline-flex", "align-items": "center", gap: "6px" }}
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" />
<polyline points="22,6 12,13 2,6" />
</svg>
Email support
</a>
<A class="btn" href="/help-center">
Browse more articles
</A>
</div>
</div>
</div>
</section>
</>
)}
</Show>
<PublicFooter />
</div>
</main>
);
}