feat(hub): landing page with product cards (hides empty products)
This commit is contained in:
184
src/pages/index.astro
Normal file
184
src/pages/index.astro
Normal file
@@ -0,0 +1,184 @@
|
||||
---
|
||||
import { getCollection } from 'astro:content';
|
||||
import '~/styles/anhh-tokens.css';
|
||||
import '@fontsource-variable/inter';
|
||||
|
||||
interface Product {
|
||||
slug: string;
|
||||
title: string;
|
||||
blurb: string;
|
||||
href: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
// Hardcoded product metadata. Adding a new product = adding to this map.
|
||||
const PRODUCT_META: Record<string, { title: string; blurb: string; firstSection: string }> = {
|
||||
whp: {
|
||||
title: 'WHP',
|
||||
blurb: 'Our hosting control panel. Add domains, create sites, manage email, set up backups.',
|
||||
firstSection: 'getting-started/welcome',
|
||||
},
|
||||
wordpress: {
|
||||
title: 'WordPress',
|
||||
blurb: 'Tips and tricks for getting the most out of WordPress on WHP.',
|
||||
firstSection: 'index',
|
||||
},
|
||||
'email-clients': {
|
||||
title: 'Email clients',
|
||||
blurb: 'Configure Outlook, Apple Mail, Thunderbird, and mobile clients.',
|
||||
firstSection: 'index',
|
||||
},
|
||||
};
|
||||
|
||||
const docs = await getCollection('docs');
|
||||
|
||||
// Group articles by top-level product folder
|
||||
const byProduct = new Map<string, number>();
|
||||
for (const entry of docs) {
|
||||
const product = entry.id.split('/')[0];
|
||||
byProduct.set(product, (byProduct.get(product) ?? 0) + 1);
|
||||
}
|
||||
|
||||
const visibleProducts: Product[] = Array.from(byProduct.entries())
|
||||
.filter(([slug]) => PRODUCT_META[slug] !== undefined)
|
||||
.filter(([, count]) => count > 0)
|
||||
.map(([slug, count]) => ({
|
||||
slug,
|
||||
title: PRODUCT_META[slug].title,
|
||||
blurb: PRODUCT_META[slug].blurb,
|
||||
href: `/${slug}/${PRODUCT_META[slug].firstSection}/`,
|
||||
count,
|
||||
}))
|
||||
.sort((a, b) => a.title.localeCompare(b.title));
|
||||
---
|
||||
|
||||
<html lang="en" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>An Honest Host Knowledge Base</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Customer documentation for WHP and other An Honest Host services."
|
||||
/>
|
||||
<link rel="canonical" href="https://kb.anhonesthost.com/" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<style>
|
||||
body {
|
||||
background: var(--anhh-bg, #0a1628);
|
||||
color: var(--anhh-text-primary, #f0f4ff);
|
||||
font-family: 'Inter Variable', 'Inter', system-ui, sans-serif;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
header,
|
||||
footer {
|
||||
padding: 1.5rem 2rem;
|
||||
}
|
||||
header {
|
||||
border-bottom: 1px solid var(--anhh-border-color, #334155);
|
||||
}
|
||||
footer {
|
||||
border-top: 1px solid var(--anhh-border-color, #334155);
|
||||
font-size: 0.875rem;
|
||||
color: var(--anhh-text-secondary, #94a3b8);
|
||||
}
|
||||
main {
|
||||
flex: 1;
|
||||
max-width: 64rem;
|
||||
margin: 0 auto;
|
||||
padding: 4rem 2rem;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
margin: 0 0 0.5rem;
|
||||
}
|
||||
.lede {
|
||||
color: var(--anhh-text-secondary, #94a3b8);
|
||||
font-size: 1.125rem;
|
||||
margin: 0 0 3rem;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
a.card {
|
||||
display: block;
|
||||
background: var(--anhh-bg-surface, #1e293b);
|
||||
border: 1px solid var(--anhh-border-color, #334155);
|
||||
border-radius: 0.75rem;
|
||||
padding: 1.5rem;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition:
|
||||
border-color 120ms ease,
|
||||
transform 120ms ease;
|
||||
}
|
||||
a.card:hover,
|
||||
a.card:focus {
|
||||
border-color: var(--anhh-accent, #00d4aa);
|
||||
transform: translateY(-2px);
|
||||
outline: none;
|
||||
}
|
||||
a.card h2 {
|
||||
margin: 0 0 0.5rem;
|
||||
font-size: 1.25rem;
|
||||
color: var(--anhh-accent, #00d4aa);
|
||||
}
|
||||
a.card p {
|
||||
margin: 0 0 0.75rem;
|
||||
color: var(--anhh-text-secondary, #94a3b8);
|
||||
}
|
||||
a.card .count {
|
||||
font-size: 0.875rem;
|
||||
color: var(--anhh-text-muted, #64748b);
|
||||
}
|
||||
.empty {
|
||||
background: var(--anhh-bg-surface, #1e293b);
|
||||
border: 1px dashed var(--anhh-border-color, #334155);
|
||||
border-radius: 0.75rem;
|
||||
padding: 2rem;
|
||||
color: var(--anhh-text-muted, #64748b);
|
||||
text-align: center;
|
||||
}
|
||||
footer a {
|
||||
color: var(--anhh-accent, #00d4aa);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header><strong>An Honest Host</strong> · Knowledge Base</header>
|
||||
<main>
|
||||
<h1>Knowledge Base</h1>
|
||||
<p class="lede">Pick a product to get started.</p>
|
||||
|
||||
{
|
||||
visibleProducts.length === 0 ? (
|
||||
<div class="empty">No documentation has been published yet.</div>
|
||||
) : (
|
||||
<div class="grid">
|
||||
{visibleProducts.map((p) => (
|
||||
<a class="card" href={p.href}>
|
||||
<h2>{p.title}</h2>
|
||||
<p>{p.blurb}</p>
|
||||
<span class="count">
|
||||
{p.count} {p.count === 1 ? 'article' : 'articles'}
|
||||
</span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</main>
|
||||
<footer>
|
||||
Need help?{' '}
|
||||
<a href="https://secure.anhonesthost.com/submitticket.php">Open a support ticket</a>. ·{' '}
|
||||
<a href="https://repo.anhonesthost.net/cloud-hosting-platform/kb-anhonesthost">Repo on Gitea</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user