← back to Nineoh Guide
apps/web/app/characters/page.tsx
54 lines
import { pool } from "@/lib/db";
import { slugify } from "@nineoh/core";
export const revalidate = 3600;
export const metadata = {
title: "Characters",
description: "Every main character on 90210 — profiles, who plays them, and the episodes they appear in.",
};
async function getCharacters() {
try {
const { rows } = await pool.query(
`select c.name, c.description_original, cp.name as actor,
(select count(*) from episode_guest_cast g where g.character_name = c.name)::int as appearances
from characters c
left join credits cr on cr.character_id = c.id and cr.credit_type='main-cast'
left join cast_people cp on cp.id = cr.person_id
where c.description_original is not null
order by c.name`
);
return rows;
} catch {
return [];
}
}
export default async function Characters() {
const chars = await getCharacters();
return (
<section>
<h1>Characters</h1>
<p style={{ color: "var(--muted)", fontSize: 13 }}>
The people of the 90210 ZIP code — who they are, who plays them, and where they turn up.
</p>
<div style={{ display: "grid", gap: 12, gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))", marginTop: 16 }}>
{chars.map((c) => (
<a key={c.name} className="card" href={`/characters/${slugify(c.name)}`} style={{ display: "block", color: "inherit" }}>
<strong style={{ fontFamily: "var(--font-display)", fontSize: 16 }}>{c.name}</strong>
{c.actor ? <span style={{ color: "var(--muted)", fontSize: 12 }}> · {c.actor}</span> : null}
<p style={{ margin: "5px 0 0", color: "var(--ink-soft)", fontSize: 12.5, lineHeight: 1.5 }}>
{c.description_original}
</p>
{c.appearances > 0 ? (
<span className="pill" style={{ marginTop: 8, display: "inline-block" }}>
{c.appearances} appearances
</span>
) : null}
</a>
))}
</div>
</section>
);
}