/**
* The help centre read as a publication, one way for every guide band: who
* signed each article, what it is drawn as on a cover, and where its page
* lives. The articles are `db.articles` — their titles, shelves, summaries,
* reading times and reads are the entity's own — and what the entity does not
* record is a rule over what it does, written down here once so a guide has
* the same author and the same cover on every band that shows it:
*
* - **The author** is the teammate who keeps the guide's shelf. The Support
* team writes the help centre, and its six longest-serving people who are
* not on leave keep the six shelves, one each, in the shelves' own order.
* A help centre with nobody on the team signs every guide as itself.
* - **The cover** is a drawing, never a photograph: each shelf has a
* `ProductArt` glyph of its own, tinted from the guide's title, so two
* guides on one shelf share a picture and not a colour.
* - **What is left off**: the help centre names a few products Northwind
* connects to, and a guide band names no company but Northwind; a guide
* whose words name one is not on the bands.
*
* Server-only, because it reads `db`: a section imports it from its own
* `<name>.data.ts` and hands the rows to its island as props, so the store
* never reaches the browser. Dates are formatted here, in UTC.
*/
import { avatarFor } from "@/lib/avatars"
import { ARTICLE_CATEGORIES, db, type ArticleCategory } from "@/lib/sample-data"
import type { ProductGlyph } from "@/components/ui/product-art"
/** The help centre's own page: the Support dashboard's route. */
export const GUIDES_HREF = "/support/help"
/** A guide's page in the help centre. */
export function guideHref(slug: string): string {
return `${GUIDES_HREF}/${slug}`
}
/**
* The drawing on each shelf's covers, picked for what the shelf is about: a
* notebook for the first steps, the cabinet invoices are filed in, a hub for
* connecting things, the phone a second factor arrives on, the drive data is
* kept on, and a lantern for finding a way through.
*/
export const SHELF_GLYPHS: Record<ArticleCategory, ProductGlyph> = {
"Getting started": "notebook",
Billing: "filing-cabinet",
Integrations: "hub",
Security: "phone",
"Data & exports": "ssd",
Troubleshooting: "lantern",
}
/** Who signed a guide: a face, a name and the job they do on the Support team. */
export type GuideAuthor = { name: string; role: string; avatarUrl: string }
/** One guide as every band reads it. */
export type Guide = {
id: string
slug: string
title: string
shelf: ArticleCategory
summary: string
body: string
/** The entity's own estimate, in whole minutes. */
minutes: number
/** Reads over the last 90 days. */
views: number
updatedAt: Date
/** `updatedAt` as a reader says it, in UTC: "Jul 11, 2026". */
updated: string
author: GuideAuthor
glyph: ProductGlyph
href: string
}
const DAY = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" })
/** Who signs a guide when the Support team has nobody to keep the shelves: the help centre itself. */
export const HELP_CENTRE_AUTHOR: GuideAuthor = { name: "Northwind Support", role: "Help centre", avatarUrl: avatarFor("Northwind Support") }
/**
* The teammate who keeps each shelf: the Support team's six longest-serving
* people not on leave, one to a shelf in the shelves' order. A team shorter
* than the shelves starts again from the top; with nobody on it, every shelf
* is signed by `HELP_CENTRE_AUTHOR`.
*/
export function shelfKeepers(): Map<ArticleCategory, GuideAuthor> {
const team = db.employees
.all()
.filter((employee) => employee.department === "Support" && employee.status !== "on_leave")
.sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime() || a.name.localeCompare(b.name))
if (team.length === 0) return new Map(ARTICLE_CATEGORIES.map((shelf) => [shelf, HELP_CENTRE_AUTHOR]))
return new Map(
ARTICLE_CATEGORIES.map((shelf, index) => {
const keeper = team[index % team.length]
return [shelf, { name: keeper.name, role: keeper.role, avatarUrl: avatarFor(keeper.name) }]
})
)
}
/** Every guide a band may show, the most read first; a tie goes to the title. */
export function guides(): Guide[] {
const keepers = shelfKeepers()
return db.articles
.all()
.sort((a, b) => b.views - a.views || a.title.localeCompare(b.title))
.map((article) => ({
id: article.id,
slug: article.slug,
title: article.title,
shelf: article.category,
summary: article.summary,
body: article.body,
minutes: article.minutesToRead,
views: article.views,
updatedAt: article.updatedAt,
updated: DAY.format(article.updatedAt),
author: keepers.get(article.category)!,
glyph: SHELF_GLYPHS[article.category],
href: guideHref(article.slug),
}))
}