All checks were successful
build-and-release / build (push) Successful in 1m40s
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>
286 lines
8.3 KiB
TypeScript
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>
|
|
);
|
|
}
|