/* ============================================================ page-help.jsx โ€” Help / API tab ============================================================ */ const _T = (window.appT || (s => s)); const QUICK_START = [ { emoji: '๐Ÿ“‹', label: _T('Sign in'), slug: 'signin-format' }, { emoji: '๐ŸŒด', label: _T('Request time off'), slug: 'request-pto' }, { emoji: 'โ˜๏ธ', label: _T('Manage VMs'), slug: 'vpsie-manage-vms' }, { emoji: '๐Ÿค–', label: _T('Ask the AI'), slug: 'ask-ai' }, ]; /* โ”€โ”€โ”€ VPSie docs collapsible card โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ function VpsieDocCard({ doc }) { const [open, setOpen] = React.useState(false); return (
setOpen(x => !x)} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '10px 14px', cursor: 'pointer', background: 'var(--bg-elev-1)', transition: 'background .1s', }} >
{doc.topic}
{!open &&
{(doc.summary || '').slice(0, 120)}{(doc.summary || '').length > 120 ? 'โ€ฆ' : ''}
}
{open ? 'โ–ฒ' : 'โ–ผ'}
{open && (
{doc.summary &&
{doc.summary}
} {(doc.steps || []).length > 0 && (
    {doc.steps.map((step, i) => (
  1. {step}
  2. ))}
)} {doc.source_url && (
{(window.appT||(s=>s))("External source")}
)}
)}
); } /* โ”€โ”€โ”€ VPSie docs section โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ function VpsieDocsSection() { const toast = useToast(); const [docs, setDocs] = React.useState([]); const [loading, setLoading] = React.useState(false); const [searchQ, setSearchQ] = React.useState(''); const load = React.useCallback(async (notify) => { setLoading(true); try { const r = await fetch('/api/vpsie-docs', { credentials: 'include' }); if (!r.ok) throw new Error(`${r.status}`); const data = await r.json(); setDocs(Array.isArray(data) ? data : []); if (notify) toast.push({ msg: (window.appT||(s=>s))('VPSie docs refreshed'), kind: 'ok', icon: 'โœ“' }); } catch (e) { toast.push({ msg: `${(window.appT||(s=>s))('VPSie docs error')}: ${e.message}`, kind: 'err', icon: '!' }); } finally { setLoading(false); } }, [toast]); React.useEffect(() => { load(false); }, []); const filtered = React.useMemo(() => { if (!searchQ.trim()) return docs; const q = searchQ.toLowerCase(); return docs.filter(d => (d.topic || '').toLowerCase().includes(q) || (d.summary || '').toLowerCase().includes(q) ); }, [docs, searchQ]); return (
s))("VPSie Documentation")} tip={(window.appT||(s=>s))("Searchable knowledge base scraped from the VPSie docs site. Re-scraped weekly by the _job_refresh_vpsie_docs cron.")} sub={(window.appT||(s=>s))("Scraped from apidocs.vpsie.com and vpsie.com/knowledge-base โ€” refreshed weekly")} right={ load(true)} loading={loading} />} /> s))("Filter VPSie topics...")} style={{ marginBottom: 12 }} /> {loading && docs.length === 0 && (
)} {!loading && filtered.length === 0 && ( s))("No VPSie topics")} subtitle={searchQ ? (window.appT||(s=>s))('Try a different search.') : (window.appT||(s=>s))('Docs will be fetched on first load.')} /> )} {filtered.map((doc, i) => ( ))}
); } function HelpPage() { const toast = useToast(); const [categories, setCategories] = React.useState([]); const [articles, setArticles] = React.useState([]); const [activeCategory, setActiveCat] = React.useState(null); const [openArticle, setOpenArticle] = React.useState(null); // full article object const [searchQ, setSearchQ] = React.useState(''); const [loading, setLoading] = React.useState(false); const [artLoading, setArtLoading] = React.useState(false); /* Load categories on mount */ React.useEffect(() => { fetch('/api/help/categories', { credentials: 'include' }) .then(r => r.ok ? r.json() : []) .then(setCategories) .catch(() => {}); }, []); /* Load articles whenever category or search changes */ const loadArticles = React.useCallback(async (cat, q, notify) => { setLoading(true); try { const p = new URLSearchParams(); if (cat) p.set('category', cat); if (q) p.set('q', q); const r = await fetch(`/api/help/articles?${p}`, { credentials: 'include' }); if (!r.ok) throw new Error(`${r.status}`); const data = await r.json(); setArticles(Array.isArray(data) ? data : []); if (notify) toast.push({ msg: (window.appT||(s=>s))('Refreshed'), kind: 'ok', icon: 'โœ“' }); } catch (e) { toast.push({ msg: `${(window.appT||(s=>s))('Help error')}: ${e.message}`, kind: 'err', icon: '!' }); } finally { setLoading(false); } }, [toast]); /* Debounced search */ const debRef = React.useRef(null); React.useEffect(() => { clearTimeout(debRef.current); debRef.current = setTimeout(() => loadArticles(activeCategory, searchQ, false), 320); return () => clearTimeout(debRef.current); }, [activeCategory, searchQ]); /* Open article by slug */ const openSlug = React.useCallback(async (slug) => { setArtLoading(true); try { const r = await fetch(`/api/help/articles/${slug}`, { credentials: 'include' }); if (r.status === 404) { toast.push({ msg: (window.appT||(s=>s))('Article not yet documented'), kind: 'warn', icon: 'ยท' }); return; } if (!r.ok) throw new Error(`${r.status}`); setOpenArticle(await r.json()); } catch (e) { toast.push({ msg: `${(window.appT||(s=>s))('Could not load article')}: ${e.message}`, kind: 'err', icon: '!' }); } finally { setArtLoading(false); } }, [toast]); const catLabel = categories.find(c => c.slug === activeCategory)?.title || (window.appT||(s=>s))('All categories'); return (
s))("Help / API")} meta={(window.appT||(s=>s))("Guides, quick-start cards, and API reference")} right={ loadArticles(activeCategory, searchQ, true)} loading={loading} />} /> {/* Search bar */} { setSearchQ(v); setOpenArticle(null); }} placeholder={(window.appT||(s=>s))("Search articles, topics, API endpoints...")} style={{ marginBottom: 20, maxWidth: '100%' }} /> {/* Quick-start tiles */} {!openArticle && !searchQ && !activeCategory && (
{QUICK_START.map(({ emoji, label, slug }) => ( ))}
)} {/* Two-column layout */}
{/* Left: category list */}
s))("Categories")} tip={(window.appT||(s=>s))("Help articles grouped by topic. Click a category to filter.")} />
{ setActiveCat(null); setOpenArticle(null); }} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '6px 4px', cursor: 'pointer', borderRadius: 'var(--r-xs)', background: activeCategory === null ? 'var(--bg-elev-2)' : 'transparent', fontSize: 12.5, marginBottom: 4, }} > {(window.appT||(s=>s))("All")} {articles.length || categories.reduce((a, c) => a + (c.article_count || 0), 0)}
{categories.map(cat => (
{ setActiveCat(cat.slug); setOpenArticle(null); }} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '6px 4px', cursor: 'pointer', borderRadius: 'var(--r-xs)', background: activeCategory === cat.slug ? 'var(--bg-elev-2)' : 'transparent', fontSize: 12.5, }} > {cat.title} {cat.article_count || 0}
))}
{/* Right: article list or article detail */}
{openArticle ? ( /* Article detail */
{/* Breadcrumb */}
setOpenArticle(null)} > {categories.find(c => c.slug === openArticle.category)?.title || (window.appT||(s=>s))('Help')} โ€บ {openArticle.title}

{openArticle.title}

{openArticle.summary}
{/* Steps */} {(openArticle.steps || []).length > 0 && (
    {openArticle.steps.map((step, i) => (
  1. {step}
  2. ))}
)} {/* Markdown fallback (for file-based docs) */} {openArticle.markdown && !openArticle.steps?.length && (
{openArticle.markdown.slice(0, 4000)}{openArticle.markdown.length > 4000 ? '\nโ€ฆ(truncated)' : ''}
)} {/* External link */} {openArticle.external_url && ( )}
) : ( /* Article list */
s))("Results for")} "${searchQ}"` : catLabel} sub={`${articles.length} ${articles.length !== 1 ? (window.appT||(s=>s))('articles') : (window.appT||(s=>s))('article')}`} /> {loading && articles.length === 0 && (
)} {!loading && articles.length === 0 && ( s))("No articles")} subtitle={searchQ ? (window.appT||(s=>s))('Try a different search.') : (window.appT||(s=>s))('Select a category or search above.')} /> )} {articles.map(art => (
openSlug(art.slug)} style={{ padding: '10px 8px', cursor: 'pointer', borderRadius: 'var(--r-sm)', transition: 'background .12s', borderTop: '1px solid var(--line)', }} className="hover-row" >
{art.title}
{art.summary}
{art.external_url && ext} {(categories.find(c => c.slug === art.category)?.title || art.category)}
))}
)}
{/* VPSie documentation โ€” DB-cached scrape of external docs */} {!openArticle && }
); } window.HelpPage = HelpPage;