feat: wire help center and article pages to live KB API
- help-center.ts: replace static HELP_ARTICLES array with async fetch* functions (fetchHelpCenterArticles, fetchHelpCenterCategories, fetchArticleBySlug); legacy sync shims kept for safety - support/index.tsx: switched from createMemo(static) to createResource(async API) with loading states - help-center/article/[slug].tsx: now fetches article from API via createResource; renders paragraphs split by double-newline; proper loading and not-found states - New server-side API routes: /api/kb/articles, /api/kb/categories, /api/kb/articles/[slug] (proxy to Rust gateway, no auth required) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
055dcd4175
commit
3209d13011
6 changed files with 287 additions and 162 deletions
|
|
@ -4,80 +4,97 @@ export type HelpArticle = {
|
||||||
title: string;
|
title: string;
|
||||||
summary: string;
|
summary: string;
|
||||||
categoryKey: string;
|
categoryKey: string;
|
||||||
|
category: string;
|
||||||
role: 'ALL' | 'company' | 'jobSeeker' | 'professional' | 'customer' | 'platform';
|
role: 'ALL' | 'company' | 'jobSeeker' | 'professional' | 'customer' | 'platform';
|
||||||
tags: string[];
|
tags: string[];
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
content: string;
|
content: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HELP_ARTICLES: HelpArticle[] = [
|
export type HelpCategory = {
|
||||||
{
|
id: string;
|
||||||
id: 'hc-1',
|
key: string;
|
||||||
slug: 'how-verification-works',
|
title: string;
|
||||||
title: 'How verification works',
|
};
|
||||||
summary: 'Understand document review steps, approval outcomes, and timeline.',
|
|
||||||
categoryKey: 'verification',
|
|
||||||
role: 'ALL',
|
|
||||||
tags: ['verification', 'documents', 'approval'],
|
|
||||||
updatedAt: '2026-03-17T00:00:00Z',
|
|
||||||
content: 'After signup, complete onboarding for one path and submit required documents. Admin review updates your status as pending, document required, approved, or rejected.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'hc-2',
|
|
||||||
slug: 'customer-post-requirement',
|
|
||||||
title: 'How customers post requirements',
|
|
||||||
summary: 'Choose profession intent, add requirements, and track verified responses.',
|
|
||||||
categoryKey: 'requirements',
|
|
||||||
role: 'customer',
|
|
||||||
tags: ['customer', 'requirements'],
|
|
||||||
updatedAt: '2026-03-17T00:00:00Z',
|
|
||||||
content: 'Customer flow starts with selecting the professional category, then requirement details, budget, and timeline. After review, qualified professionals can respond.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'hc-3',
|
|
||||||
slug: 'professional-onboarding-guide',
|
|
||||||
title: 'Professional onboarding guide',
|
|
||||||
summary: 'Choose your profession, upload portfolio, submit PDF ID documents, and wait for approval.',
|
|
||||||
categoryKey: 'onboarding',
|
|
||||||
role: 'professional',
|
|
||||||
tags: ['professional', 'onboarding', 'portfolio'],
|
|
||||||
updatedAt: '2026-03-17T00:00:00Z',
|
|
||||||
content: 'Each profession in Solid has its own onboarding and service configuration. Complete all steps and verification to unlock your full dashboard.',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export function listHelpCenterArticles(input: { role?: string; categoryKey?: string; q?: string }) {
|
// ── API fetchers ──────────────────────────────────────────────────────────────
|
||||||
const role = String(input.role || 'ALL');
|
|
||||||
const categoryKey = String(input.categoryKey || '').trim();
|
|
||||||
const q = String(input.q || '').trim().toLowerCase();
|
|
||||||
|
|
||||||
return HELP_ARTICLES.filter((article) => {
|
export async function fetchHelpCenterArticles(input: {
|
||||||
const roleOk = role === 'ALL' || article.role === 'ALL' || article.role === role;
|
role?: string;
|
||||||
const categoryOk = !categoryKey || article.categoryKey === categoryKey;
|
categoryKey?: string;
|
||||||
const queryOk = !q || `${article.title} ${article.summary} ${article.tags.join(' ')}`.toLowerCase().includes(q);
|
q?: string;
|
||||||
return roleOk && categoryOk && queryOk;
|
}): Promise<HelpArticle[]> {
|
||||||
});
|
const params = new URLSearchParams();
|
||||||
}
|
if (input.role && input.role !== 'ALL') params.set('role', input.role);
|
||||||
|
if (input.categoryKey) params.set('category', input.categoryKey);
|
||||||
|
if (input.q) params.set('q', input.q);
|
||||||
|
|
||||||
export function listHelpCenterCategories() {
|
try {
|
||||||
const keys = new Map<string, string>();
|
const res = await fetch(`/api/kb/articles?${params.toString()}`);
|
||||||
for (const article of HELP_ARTICLES) {
|
if (!res.ok) return [];
|
||||||
if (!keys.has(article.categoryKey)) {
|
const data = await res.json();
|
||||||
const title = article.categoryKey
|
const raw: any[] = Array.isArray(data) ? data : (data.articles ?? []);
|
||||||
.split('-')
|
return raw.map(normalizeArticle);
|
||||||
.map((chunk) => chunk.charAt(0).toUpperCase() + chunk.slice(1))
|
} catch {
|
||||||
.join(' ');
|
return [];
|
||||||
keys.set(article.categoryKey, title);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.from(keys.entries()).map(([key, title], idx) => ({
|
export async function fetchHelpCenterCategories(): Promise<HelpCategory[]> {
|
||||||
id: `cat-${idx + 1}`,
|
try {
|
||||||
key,
|
const res = await fetch('/api/kb/categories');
|
||||||
title,
|
if (!res.ok) return [];
|
||||||
|
const data = await res.json();
|
||||||
|
const raw: any[] = Array.isArray(data) ? data : (data.categories ?? []);
|
||||||
|
return raw.map((c) => ({
|
||||||
|
id: c.id,
|
||||||
|
key: c.slug,
|
||||||
|
title: c.name,
|
||||||
}));
|
}));
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getArticleBySlug(slug: string) {
|
export async function fetchArticleBySlug(slug: string): Promise<HelpArticle | null> {
|
||||||
return HELP_ARTICLES.find((article) => article.slug === slug) || null;
|
try {
|
||||||
|
const res = await fetch(`/api/kb/articles/${slug}`);
|
||||||
|
if (!res.ok) return null;
|
||||||
|
const data = await res.json();
|
||||||
|
return normalizeArticle(data);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Normalizer ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function normalizeArticle(raw: any): HelpArticle {
|
||||||
|
return {
|
||||||
|
id: raw.id ?? '',
|
||||||
|
slug: raw.slug ?? '',
|
||||||
|
title: raw.title ?? '',
|
||||||
|
summary: raw.summary ?? raw.content?.slice(0, 160) ?? '',
|
||||||
|
categoryKey: raw.categoryKey ?? raw.category_key ?? raw.categorySlug ?? '',
|
||||||
|
category: raw.category ?? raw.categoryKey ?? '',
|
||||||
|
role: (raw.role ?? 'ALL') as HelpArticle['role'],
|
||||||
|
tags: Array.isArray(raw.tags) ? raw.tags : [],
|
||||||
|
updatedAt: raw.updatedAt ?? raw.updated_at ?? new Date().toISOString(),
|
||||||
|
content: raw.content ?? raw.body ?? '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Legacy sync shims (kept for any remaining call sites) ─────────────────────
|
||||||
|
// These return empty data synchronously — pages should use the async fetch* functions above.
|
||||||
|
|
||||||
|
export function listHelpCenterArticles(_input: { role?: string; categoryKey?: string; q?: string }): HelpArticle[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listHelpCenterCategories(): HelpCategory[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getArticleBySlug(_slug: string): HelpArticle | null {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
src/routes/api/kb/articles.ts
Normal file
19
src/routes/api/kb/articles.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { gatewayUrl } from '~/lib/server/gateway';
|
||||||
|
|
||||||
|
export async function GET({ request }: { request: Request }) {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const upstream = gatewayUrl('/api/kb/articles' + url.search);
|
||||||
|
try {
|
||||||
|
const res = await fetch(upstream, { cache: 'no-store' });
|
||||||
|
const body = await res.text();
|
||||||
|
return new Response(body, {
|
||||||
|
status: res.status,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
return new Response(JSON.stringify({ error: err?.message || 'Gateway error' }), {
|
||||||
|
status: 502,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/routes/api/kb/articles/[slug].ts
Normal file
18
src/routes/api/kb/articles/[slug].ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { gatewayUrl } from '~/lib/server/gateway';
|
||||||
|
|
||||||
|
export async function GET({ params }: { params: { slug: string } }) {
|
||||||
|
const upstream = gatewayUrl(`/api/kb/articles/${params.slug}`);
|
||||||
|
try {
|
||||||
|
const res = await fetch(upstream, { cache: 'no-store' });
|
||||||
|
const body = await res.text();
|
||||||
|
return new Response(body, {
|
||||||
|
status: res.status,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
return new Response(JSON.stringify({ error: err?.message || 'Gateway error' }), {
|
||||||
|
status: 502,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/routes/api/kb/categories.ts
Normal file
18
src/routes/api/kb/categories.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { gatewayUrl } from '~/lib/server/gateway';
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const upstream = gatewayUrl('/api/kb/categories');
|
||||||
|
try {
|
||||||
|
const res = await fetch(upstream, { cache: 'no-store' });
|
||||||
|
const body = await res.text();
|
||||||
|
return new Response(body, {
|
||||||
|
status: res.status,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
return new Response(JSON.stringify({ error: err?.message || 'Gateway error' }), {
|
||||||
|
status: 502,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { A, useParams } from '@solidjs/router';
|
import { A, useParams } from '@solidjs/router';
|
||||||
import { createSignal, onCleanup, onMount } from 'solid-js';
|
import { Show, For, createSignal, createResource, onCleanup, onMount } from 'solid-js';
|
||||||
import { getArticleBySlug } from '~/lib/help-center';
|
import { fetchArticleBySlug } from '~/lib/help-center';
|
||||||
import PublicBackground from '~/components/PublicBackground';
|
import PublicBackground from '~/components/PublicBackground';
|
||||||
import PublicHeader from '~/components/PublicHeader';
|
import PublicHeader from '~/components/PublicHeader';
|
||||||
import PublicFooter from '~/components/PublicFooter';
|
import PublicFooter from '~/components/PublicFooter';
|
||||||
|
|
@ -15,9 +15,10 @@ function categoryTitle(input: string) {
|
||||||
|
|
||||||
export default function HelpCenterArticlePage() {
|
export default function HelpCenterArticlePage() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const article = getArticleBySlug(params.slug || '');
|
|
||||||
const [scrollY, setScrollY] = createSignal(0);
|
const [scrollY, setScrollY] = createSignal(0);
|
||||||
|
|
||||||
|
const [article] = createResource(() => params.slug, fetchArticleBySlug);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const onScroll = () => setScrollY(window.scrollY || 0);
|
const onScroll = () => setScrollY(window.scrollY || 0);
|
||||||
onScroll();
|
onScroll();
|
||||||
|
|
@ -25,12 +26,21 @@ export default function HelpCenterArticlePage() {
|
||||||
onCleanup(() => window.removeEventListener('scroll', onScroll));
|
onCleanup(() => window.removeEventListener('scroll', onScroll));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!article) {
|
|
||||||
return (
|
return (
|
||||||
<main class="lp-main">
|
<main class="lp-main">
|
||||||
<PublicBackground scrollY={scrollY()} />
|
<PublicBackground scrollY={scrollY()} />
|
||||||
<div class="lp-content">
|
<div class="lp-content">
|
||||||
<PublicHeader />
|
<PublicHeader />
|
||||||
|
|
||||||
|
<Show when={article.loading}>
|
||||||
|
<section class="public-section scene-dark">
|
||||||
|
<div class="container panel panel-light" style={{ 'max-width': '960px' }}>
|
||||||
|
<p style="color:#94a3b8;padding:40px 0;text-align:center">Loading article…</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<Show when={!article.loading && !article()}>
|
||||||
<section class="public-section scene-dark">
|
<section class="public-section scene-dark">
|
||||||
<div class="container panel panel-light">
|
<div class="container panel panel-light">
|
||||||
<h1 class="title">Article not found</h1>
|
<h1 class="title">Article not found</h1>
|
||||||
|
|
@ -40,35 +50,34 @@ export default function HelpCenterArticlePage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<PublicFooter />
|
</Show>
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
<Show when={!article.loading && article()}>
|
||||||
<main class="lp-main">
|
{(a) => (
|
||||||
<PublicBackground scrollY={scrollY()} />
|
<>
|
||||||
<div class="lp-content">
|
|
||||||
<PublicHeader />
|
|
||||||
<section class="public-section scene-dark">
|
<section class="public-section scene-dark">
|
||||||
<div class="container panel panel-light" style={{ 'max-width': '960px' }}>
|
<div class="container panel panel-light" style={{ 'max-width': '960px' }}>
|
||||||
<p class="eyebrow">{categoryTitle(article.categoryKey)}</p>
|
<p class="eyebrow">{a().category || categoryTitle(a().categoryKey)}</p>
|
||||||
<h1 class="title">{article.title}</h1>
|
<h1 class="title">{a().title}</h1>
|
||||||
<p class="subtitle">{article.summary}</p>
|
<p class="subtitle">{a().summary}</p>
|
||||||
|
|
||||||
|
<Show when={a().tags.length > 0}>
|
||||||
<div class="help-article-tags" style={{ 'margin-top': '10px' }}>
|
<div class="help-article-tags" style={{ 'margin-top': '10px' }}>
|
||||||
{article.tags.map((tag) => <span class="help-article-tag">{tag}</span>)}
|
<For each={a().tags}>{(tag) => <span class="help-article-tag">{tag}</span>}</For>
|
||||||
</div>
|
</div>
|
||||||
<p class="note">Updated {new Date(article.updatedAt).toLocaleDateString()}</p>
|
</Show>
|
||||||
|
|
||||||
|
<p class="note">Updated {new Date(a().updatedAt).toLocaleDateString()}</p>
|
||||||
|
|
||||||
<div class="help-article-body">
|
<div class="help-article-body">
|
||||||
<p>{article.content}</p>
|
<For each={a().content.split('\n\n').filter(Boolean)}>
|
||||||
|
{(para) => <p>{para}</p>}
|
||||||
|
</For>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<A class="btn" href="/help-center">Back to Help Center</A>
|
<A class="btn" href="/help-center">Back to Help Center</A>
|
||||||
<A class="btn primary" href="/auth/register?intent=customer&redirect=/users/onboarding/customer">Get Started</A>
|
<A class="btn primary" href="/auth/register">Get Started</A>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -76,13 +85,20 @@ export default function HelpCenterArticlePage() {
|
||||||
<section class="public-section scene-dark">
|
<section class="public-section scene-dark">
|
||||||
<div class="container panel panel-light" style={{ 'max-width': '960px' }}>
|
<div class="container panel panel-light" style={{ 'max-width': '960px' }}>
|
||||||
<h2>Need more help?</h2>
|
<h2>Need more help?</h2>
|
||||||
<p class="sub">If this article does not solve your issue, send your question with context to support.</p>
|
<p class="sub">
|
||||||
|
If this article does not solve your issue, send your question with context to support.
|
||||||
|
</p>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<a class="btn primary" href="mailto:support@nxtgauge.com?subject=Nxtgauge%20Help%20Center%20Question">Email support</a>
|
<a class="btn primary" href="mailto:support@nxtgauge.com?subject=Nxtgauge%20Help%20Center%20Question">
|
||||||
|
Email support
|
||||||
|
</a>
|
||||||
<A class="btn" href="/help-center">Browse more articles</A>
|
<A class="btn" href="/help-center">Browse more articles</A>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
|
|
||||||
<PublicFooter />
|
<PublicFooter />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { A, useSearchParams } from '@solidjs/router';
|
import { A, useSearchParams } from '@solidjs/router';
|
||||||
import { For, createMemo, createSignal, onCleanup, onMount } from 'solid-js';
|
import { For, Show, createMemo, createResource, createSignal, onCleanup, onMount } from 'solid-js';
|
||||||
import { listHelpCenterArticles, listHelpCenterCategories } from '~/lib/help-center';
|
import { fetchHelpCenterArticles, fetchHelpCenterCategories } from '~/lib/help-center';
|
||||||
import PublicBackground from '~/components/PublicBackground';
|
import PublicBackground from '~/components/PublicBackground';
|
||||||
import PublicHeader from '~/components/PublicHeader';
|
import PublicHeader from '~/components/PublicHeader';
|
||||||
import PublicFooter from '~/components/PublicFooter';
|
import PublicFooter from '~/components/PublicFooter';
|
||||||
|
|
@ -30,22 +30,38 @@ export default function SupportPage() {
|
||||||
const category = createMemo(() => String(search.category || ''));
|
const category = createMemo(() => String(search.category || ''));
|
||||||
const q = createMemo(() => String(search.q || ''));
|
const q = createMemo(() => String(search.q || ''));
|
||||||
|
|
||||||
const categories = createMemo(() => listHelpCenterCategories());
|
const [categories] = createResource(fetchHelpCenterCategories);
|
||||||
const articles = createMemo(() =>
|
|
||||||
listHelpCenterArticles({ role: role(), categoryKey: category() || undefined, q: q() || undefined }),
|
const articleParams = createMemo(() => ({
|
||||||
);
|
role: role(),
|
||||||
|
categoryKey: category() || undefined,
|
||||||
|
q: q() || undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const [articles] = createResource(articleParams, (p) => fetchHelpCenterArticles(p));
|
||||||
|
|
||||||
const visibleCategories = createMemo(() => {
|
const visibleCategories = createMemo(() => {
|
||||||
if (categories().length > 0) return categories();
|
const cats = categories();
|
||||||
|
if (cats && cats.length > 0) return cats;
|
||||||
|
const arts = articles();
|
||||||
|
if (!arts) return [];
|
||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
return articles()
|
return arts
|
||||||
.filter((item) => {
|
.filter((item) => {
|
||||||
if (seen.has(item.categoryKey)) return false;
|
if (seen.has(item.categoryKey)) return false;
|
||||||
seen.add(item.categoryKey);
|
seen.add(item.categoryKey);
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
.map((item, idx) => ({ id: `derived-${idx + 1}`, key: item.categoryKey, title: categoryTitle(item.categoryKey) }));
|
.map((item, idx) => ({
|
||||||
|
id: `derived-${idx + 1}`,
|
||||||
|
key: item.categoryKey,
|
||||||
|
title: item.category || categoryTitle(item.categoryKey),
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
const categoryName = createMemo(() => visibleCategories().find((cat) => cat.key === category())?.title || categoryTitle(category()));
|
|
||||||
|
const categoryName = createMemo(
|
||||||
|
() => visibleCategories().find((cat) => cat.key === category())?.title || categoryTitle(category()),
|
||||||
|
);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const onScroll = () => setScrollY(window.scrollY || 0);
|
const onScroll = () => setScrollY(window.scrollY || 0);
|
||||||
|
|
@ -66,7 +82,7 @@ export default function SupportPage() {
|
||||||
<p class="eyebrow">Help Center</p>
|
<p class="eyebrow">Help Center</p>
|
||||||
<h1 class="title">Get answers quickly</h1>
|
<h1 class="title">Get answers quickly</h1>
|
||||||
<p class="subtitle">
|
<p class="subtitle">
|
||||||
Articles are loaded at runtime from published Help Center management content so public users always see the latest approved guidance.
|
Browse articles by role or category, or search for what you need.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<form method="GET" action="/help-center" class="help-search-grid">
|
<form method="GET" action="/help-center" class="help-search-grid">
|
||||||
|
|
@ -82,16 +98,20 @@ export default function SupportPage() {
|
||||||
|
|
||||||
<div class="help-category-head">
|
<div class="help-category-head">
|
||||||
<p class="help-category-kicker">Categories</p>
|
<p class="help-category-kicker">Categories</p>
|
||||||
{category() && (
|
<Show when={category()}>
|
||||||
<A
|
<A
|
||||||
class="help-clear-filter"
|
class="help-clear-filter"
|
||||||
href={`/help-center?${new URLSearchParams({ ...(role() !== 'ALL' ? { role: role() } : {}), ...(q() ? { q: q() } : {}) }).toString()}`}
|
href={`/help-center?${new URLSearchParams({ ...(role() !== 'ALL' ? { role: role() } : {}), ...(q() ? { q: q() } : {}) }).toString()}`}
|
||||||
>
|
>
|
||||||
Clear category filter
|
Clear category filter
|
||||||
</A>
|
</A>
|
||||||
)}
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Show when={categories.loading}>
|
||||||
|
<div class="help-category-row" style="color:#94a3b8;font-size:14px">Loading categories…</div>
|
||||||
|
</Show>
|
||||||
|
<Show when={!categories.loading}>
|
||||||
<div class="help-category-row">
|
<div class="help-category-row">
|
||||||
<For each={visibleCategories()}>
|
<For each={visibleCategories()}>
|
||||||
{(cat) => (
|
{(cat) => (
|
||||||
|
|
@ -104,6 +124,7 @@ export default function SupportPage() {
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -117,16 +138,23 @@ export default function SupportPage() {
|
||||||
? 'Latest articles'
|
? 'Latest articles'
|
||||||
: `${ROLE_LABELS[role()] || 'Role'} articles`}
|
: `${ROLE_LABELS[role()] || 'Role'} articles`}
|
||||||
</h2>
|
</h2>
|
||||||
<span>{articles().length} articles</span>
|
<span>{articles()?.length ?? 0} articles</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Show when={articles.loading}>
|
||||||
|
<div style="padding:40px 0;text-align:center;color:#94a3b8">Loading articles…</div>
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<Show when={!articles.loading}>
|
||||||
<div class="help-article-list">
|
<div class="help-article-list">
|
||||||
<For each={articles()}>
|
<For each={articles() ?? []}>
|
||||||
{(article) => (
|
{(article) => (
|
||||||
<article class="help-article-card">
|
<article class="help-article-card">
|
||||||
<p class="note">{categoryTitle(article.categoryKey)}</p>
|
<p class="note">{article.category || categoryTitle(article.categoryKey)}</p>
|
||||||
<h3>
|
<h3>
|
||||||
<A class="help-article-link" href={`/help-center/article/${article.slug}`}>{article.title}</A>
|
<A class="help-article-link" href={`/help-center/article/${article.slug}`}>
|
||||||
|
{article.title}
|
||||||
|
</A>
|
||||||
</h3>
|
</h3>
|
||||||
<p class="help-article-summary">{article.summary}</p>
|
<p class="help-article-summary">{article.summary}</p>
|
||||||
<div class="help-article-tags">
|
<div class="help-article-tags">
|
||||||
|
|
@ -136,13 +164,18 @@ export default function SupportPage() {
|
||||||
</div>
|
</div>
|
||||||
<div class="help-article-meta">
|
<div class="help-article-meta">
|
||||||
<span>Updated {new Date(article.updatedAt).toLocaleDateString()}</span>
|
<span>Updated {new Date(article.updatedAt).toLocaleDateString()}</span>
|
||||||
<A class="help-read-link" href={`/help-center/article/${article.slug}`}>Read article</A>
|
<A class="help-read-link" href={`/help-center/article/${article.slug}`}>
|
||||||
|
Read article
|
||||||
|
</A>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
{articles().length === 0 && <article class="help-empty-card">No Help Center articles matched your filters.</article>}
|
<Show when={(articles()?.length ?? 0) === 0}>
|
||||||
|
<article class="help-empty-card">No Help Center articles matched your filters.</article>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -151,10 +184,14 @@ export default function SupportPage() {
|
||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">Still have questions?</p>
|
<p class="eyebrow">Still have questions?</p>
|
||||||
<h2>Ask the support team</h2>
|
<h2>Ask the support team</h2>
|
||||||
<p class="sub">Share your role, what you tried, and which article you checked so support can respond faster.</p>
|
<p class="sub">
|
||||||
|
Share your role, what you tried, and which article you checked so support can respond faster.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="hero-actions">
|
<div class="hero-actions">
|
||||||
<a class="lp-primary-btn" href="mailto:support@nxtgauge.com?subject=Nxtgauge%20Help%20Center%20Question">Email support</a>
|
<a class="lp-primary-btn" href="mailto:support@nxtgauge.com?subject=Nxtgauge%20Help%20Center%20Question">
|
||||||
|
Email support
|
||||||
|
</a>
|
||||||
<A class="lp-ghost-btn" href="/contact">Contact page</A>
|
<A class="lp-ghost-btn" href="/contact">Contact page</A>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue