nxtgauge-frontend-solid/src/components/dashboard/HelpCenterDashboardPage.tsx
Tracewebstudio Dev 49b979eb16
All checks were successful
build-and-release / build (push) Successful in 1m40s
fix: eliminate API prefix double-encoding across all dashboard components
All dashboard pages and widgets had one of three bugs:
- const API = "/api" combined with paths already starting /api → double prefix
- const API = '/api/gateway' → nonexistent gateway path prefix
- cleanPath stripping /api off paths when API was set to ""

Fix: set const API = "" uniformly and remove cleanPath rewrite in all 30+
affected files (CompanyJobsPage, CompanyApplicationsPage, CreditsPage,
JobSeekerJobsPage, CustomerRequirementsPage, all widgets, etc.).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-18 11:40:20 +02:00

286 lines
8.3 KiB
TypeScript

import { For, Show, createMemo, createSignal, onMount } from "solid-js";
import { HelpCircle } from "lucide-solid";
import { BTN_GHOST, CARD, INPUT } from "~/components/DashboardShell";
import { type RoleKey } from "./RoleDashboardShared";
const API = "";
const NAVY = "#0D0D2A";
const ORANGE = "#FF5E13";
type Props = { roleKey: RoleKey };
type Category = { id: string; name: string; slug: string; description?: string };
type Article = {
id: string;
title: string;
slug: string;
summary?: string;
category?: string;
updatedAt?: string;
};
async function apiFetch(path: string, opts?: RequestInit) {
const token =
typeof window !== "undefined"
? window.sessionStorage.getItem("nxtgauge_access_token") || ""
: "";
return fetch(`${API}${path}`, {
...opts,
credentials: "include",
headers: {
"Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
...(opts?.headers ?? {}),
},
});
}
function normalizeArticle(raw: any): Article {
return {
id: String(raw?.id || ""),
title: String(raw?.title || ""),
slug: String(raw?.slug || ""),
summary: raw?.summary ? String(raw.summary) : "",
category: String(raw?.category || raw?.category_name || ""),
updatedAt: String(raw?.updatedAt || raw?.updated_at || ""),
};
}
export default function HelpCenterDashboardPage(props: Props) {
const [categories, setCategories] = createSignal<Category[]>([]);
const [articles, setArticles] = createSignal<Article[]>([]);
const [loading, setLoading] = createSignal(true);
const [search, setSearch] = createSignal("");
const [err, setErr] = createSignal("");
const loadData = async () => {
setLoading(true);
setErr("");
try {
const [catRes, artRes] = await Promise.all([
apiFetch("/api/kb/categories"),
apiFetch(`/api/kb/articles?role=${encodeURIComponent(props.roleKey)}&page=1&limit=200`),
]);
const catJson = await catRes.json().catch(() => ({}));
const artJson = await artRes.json().catch(() => ({}));
if (catRes.ok) {
const catList = Array.isArray(catJson?.categories)
? catJson.categories
: Array.isArray(catJson)
? catJson
: [];
setCategories(catList);
}
if (artRes.ok) {
const rawArticles = Array.isArray(artJson?.articles)
? artJson.articles
: Array.isArray(artJson)
? artJson
: [];
setArticles(
rawArticles
.map(normalizeArticle)
.filter((a: Article) => Boolean(a.id && a.slug && a.title))
);
}
if (!catRes.ok && !artRes.ok) setErr("Failed to load help center resources.");
} catch {
setErr("Network error while loading help center.");
} finally {
setLoading(false);
}
};
onMount(loadData);
const filtered = createMemo(() => {
const q = search().trim().toLowerCase();
if (!q) return articles();
return articles().filter(
(a) =>
String(a.title || "")
.toLowerCase()
.includes(q) ||
String(a.summary || "")
.toLowerCase()
.includes(q) ||
String(a.category || "")
.toLowerCase()
.includes(q)
);
});
return (
<div style={{ display: "grid", gap: "14px", "max-width": "980px" }}>
<div
style={{
background: NAVY,
"border-radius": "12px",
padding: "16px 20px",
display: "flex",
"align-items": "center",
gap: "12px",
}}
>
<span style={{ color: ORANGE }}>
<HelpCircle size={24} />
</span>
<div>
<p style={{ margin: "0", "font-size": "18px", "font-weight": "800", color: "#fff" }}>
Help Center
</p>
<p style={{ margin: "4px 0 0", "font-size": "13px", color: "rgba(255,255,255,0.65)" }}>
Find guides and articles for your role.
</p>
</div>
</div>
<Show when={err()}>
<div
style={{
...CARD,
border: "1px solid #FECACA",
background: "#FEF2F2",
padding: "12px 14px",
color: "#B91C1C",
"font-size": "13px",
"font-weight": "600",
}}
>
{err()}
</div>
</Show>
<div style={{ ...CARD, display: "grid", gap: "10px" }}>
<div style={{ display: "flex", gap: "10px", "align-items": "center" }}>
<input
value={search()}
onInput={(e) => setSearch(e.currentTarget.value)}
style={INPUT}
placeholder="Search help articles"
/>
<button type="button" onClick={loadData} style={BTN_GHOST}>
Refresh
</button>
</div>
<Show when={loading()}>
<p style={{ margin: "0", color: "#9CA3AF", "font-size": "13px" }}>
Loading help center...
</p>
</Show>
</div>
<Show when={!loading() && categories().length > 0}>
<div style={CARD}>
<p
style={{
margin: "0 0 10px",
"font-size": "16px",
"font-weight": "700",
color: "#111827",
}}
>
Categories
</p>
<div
style={{
display: "grid",
"grid-template-columns": "repeat(3,minmax(0,1fr))",
gap: "10px",
}}
>
<For each={categories().slice(0, 6)}>
{(cat) => (
<div
style={{
border: "1px solid #E5E7EB",
"border-radius": "10px",
padding: "10px",
background: "#FCFCFD",
}}
>
<p
style={{
margin: "0",
"font-size": "13px",
"font-weight": "700",
color: "#111827",
}}
>
{cat.name}
</p>
<p style={{ margin: "4px 0 0", "font-size": "12px", color: "#6B7280" }}>
{cat.description || "Knowledge base category"}
</p>
</div>
)}
</For>
</div>
</div>
</Show>
<div style={CARD}>
<p
style={{
margin: "0 0 10px",
"font-size": "16px",
"font-weight": "700",
color: "#111827",
}}
>
Articles
</p>
<Show when={!loading() && filtered().length === 0}>
<p style={{ margin: "0", color: "#6B7280", "font-size": "13px" }}>No articles found.</p>
</Show>
<Show when={filtered().length > 0}>
<div style={{ display: "flex", "flex-direction": "column", gap: "10px" }}>
<For each={filtered()}>
{(a) => (
<a
href={`/help-center/article/${a.slug}`}
style={{
display: "flex",
"flex-direction": "column",
border: "1px solid #E5E7EB",
"border-radius": "10px",
padding: "12px 14px",
background: "#FCFCFD",
color: "inherit",
"text-decoration": "none",
"text-align": "left",
}}
>
<p
style={{
margin: "0",
"font-size": "14px",
"font-weight": "700",
color: "#111827",
"line-height": "1.4",
}}
>
{a.title}
</p>
<p
style={{
margin: "6px 0 0",
"font-size": "13px",
color: "#6B7280",
"line-height": "1.5",
}}
>
{a.summary || "Open article"}
</p>
</a>
)}
</For>
</div>
</Show>
</div>
</div>
);
}