import { createResource, createSignal, createMemo, Show, For } from 'solid-js'; import { A } from '@solidjs/router'; import AdminShell from '~/components/AdminShell'; const API = '/api/gateway'; type KbCategory = { id: string; name: string; slug: string; description?: string; article_count?: number; updated_at?: string; }; type KbArticle = { id: string; title: string; slug?: string; category_id?: string; category?: string; status: string; updated_at?: string; }; async function loadCategories(): Promise { try { const res = await fetch(`${API}/api/admin/kb/categories`); if (!res.ok) throw new Error('Failed to load'); const data = await res.json(); return Array.isArray(data) ? data : (data.categories || []); } catch { return []; } } async function loadArticles(): Promise { try { const res = await fetch(`${API}/api/admin/kb/articles`); if (!res.ok) throw new Error('Failed to load'); const data = await res.json(); return Array.isArray(data) ? data : (data.articles || []); } catch { return []; } } export default function KbPage() { const [tab, setTab] = createSignal<'categories' | 'articles' | 'create-article'>('categories'); // Categories resource const [categories, { refetch: refetchCategories }] = createResource(loadCategories); // Articles resource const [articles, { refetch: refetchArticles }] = createResource(loadArticles); // --- Categories tab state --- const [showCatForm, setShowCatForm] = createSignal(false); const [catName, setCatName] = createSignal(''); const [catSlug, setCatSlug] = createSignal(''); const [catDesc, setCatDesc] = createSignal(''); const [catSaving, setCatSaving] = createSignal(false); const [catError, setCatError] = createSignal(''); const [editingCatId, setEditingCatId] = createSignal(''); const [editCatName, setEditCatName] = createSignal(''); const [editCatSlug, setEditCatSlug] = createSignal(''); const [editCatSaving, setEditCatSaving] = createSignal(false); const [editCatError, setEditCatError] = createSignal(''); const [deletingCatId, setDeletingCatId] = createSignal(''); const [actionError, setActionError] = createSignal(''); // --- Articles tab state --- const [articleSearch, setArticleSearch] = createSignal(''); const [deletingArticleId, setDeletingArticleId] = createSignal(''); const [articleActionError, setArticleActionError] = createSignal(''); // --- Create Article tab state --- const [artTitle, setArtTitle] = createSignal(''); const [artSlug, setArtSlug] = createSignal(''); const [artCategoryId, setArtCategoryId] = createSignal(''); const [artContent, setArtContent] = createSignal(''); const [artStatus, setArtStatus] = createSignal('DRAFT'); const [artSaving, setArtSaving] = createSignal(false); const [artError, setArtError] = createSignal(''); // Filtered articles const filteredArticles = createMemo(() => { const all = articles() ?? []; const q = articleSearch().toLowerCase(); if (!q) return all; return all.filter((a) => a.title?.toLowerCase().includes(q)); }); // Categories actions const handleAddCategory = async (e: Event) => { e.preventDefault(); try { setCatSaving(true); setCatError(''); const res = await fetch(`${API}/api/admin/kb/categories`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: catName(), slug: catSlug(), description: catDesc() }), }); if (!res.ok) throw new Error('Failed to create category'); setCatName(''); setCatSlug(''); setCatDesc(''); setShowCatForm(false); refetchCategories(); } catch (err: any) { setCatError(err.message || 'Failed to create'); } finally { setCatSaving(false); } }; const startEditCat = (cat: KbCategory) => { setEditingCatId(cat.id); setEditCatName(cat.name); setEditCatSlug(cat.slug); setEditCatError(''); }; const cancelEditCat = () => { setEditingCatId(''); setEditCatError(''); }; const saveEditCat = async (id: string) => { try { setEditCatSaving(true); setEditCatError(''); const res = await fetch(`${API}/api/admin/kb/categories/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: editCatName(), slug: editCatSlug() }), }); if (!res.ok) throw new Error('Failed to save'); setEditingCatId(''); refetchCategories(); } catch (err: any) { setEditCatError(err.message || 'Failed to save'); } finally { setEditCatSaving(false); } }; const deleteCategory = async (id: string, name: string) => { if (!confirm(`Delete category "${name}"?`)) return; try { setDeletingCatId(id); setActionError(''); const res = await fetch(`${API}/api/admin/kb/categories/${id}`, { method: 'DELETE' }); if (!res.ok) throw new Error('Failed to delete'); refetchCategories(); } catch (err: any) { setActionError(err.message || 'Failed to delete'); } finally { setDeletingCatId(''); } }; // Articles actions const deleteArticle = async (id: string, title: string) => { if (!confirm(`Delete article "${title}"?`)) return; try { setDeletingArticleId(id); setArticleActionError(''); const res = await fetch(`${API}/api/admin/kb/articles/${id}`, { method: 'DELETE' }); if (!res.ok) throw new Error('Failed to delete'); refetchArticles(); } catch (err: any) { setArticleActionError(err.message || 'Failed to delete'); } finally { setDeletingArticleId(''); } }; // Create article const handleCreateArticle = async (e: Event) => { e.preventDefault(); try { setArtSaving(true); setArtError(''); const body: Record = { title: artTitle(), slug: artSlug(), content: artContent(), status: artStatus(), }; if (artCategoryId()) body.category_id = artCategoryId(); const res = await fetch(`${API}/api/admin/kb/articles`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); if (!res.ok) throw new Error('Failed to create article'); setArtTitle(''); setArtSlug(''); setArtCategoryId(''); setArtContent(''); setArtStatus('DRAFT'); setTab('articles'); refetchArticles(); } catch (err: any) { setArtError(err.message || 'Failed to create'); } finally { setArtSaving(false); } }; return (

Knowledge Base

Manage help articles and categories

{/* Tabs */}
{/* Categories Tab */}
{actionError()}

New Category

{catError()}
setCatName(e.currentTarget.value)} required placeholder="e.g. Getting Started" style="width:100%;padding:8px 10px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px" />
setCatSlug(e.currentTarget.value)} required placeholder="e.g. getting-started" style="width:100%;padding:8px 10px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px" />