- 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
565 lines
22 KiB
TypeScript
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>
|
|
);
|
|
}
|