← back to Nineoh Guide
apps/web/app/news/page.tsx
109 lines
import { pool } from "@/lib/db";
import { NewsControls } from "@/components/NewsControls";
export const dynamic = "force-dynamic";
export const metadata = { title: "News" };
const PER_PAGE = 60;
const ORDER: Record<string, string> = {
newest: "published_at desc nulls last",
oldest: "published_at asc nulls last",
source: "publisher_name asc nulls last, published_at desc",
};
async function getNews(offset: number, sort: string) {
try {
const total = await pool.query(`select count(*)::int as n from news_items`);
const { rows } = await pool.query(
`select id, headline, summary_original, canonical_url, publisher_name, published_at
from news_items
order by ${ORDER[sort] ?? ORDER.newest}
limit $1 offset $2`,
[PER_PAGE, offset]
);
return { rows, total: total.rows[0]?.n ?? 0 };
} catch {
return { rows: [], total: 0 };
}
}
export default async function News({
searchParams,
}: {
searchParams: Promise<{ page?: string; sort?: string }>;
}) {
const { page, sort: sortRaw } = await searchParams;
const sort = sortRaw && ORDER[sortRaw] ? sortRaw : "newest";
const p = Math.max(1, Number(page) || 1);
const { rows: news, total } = await getNews((p - 1) * PER_PAGE, sort);
const pages = Math.ceil(total / PER_PAGE);
const q = (n: number) => `/news?sort=${sort}&page=${n}`;
return (
<section>
<h1>90210 in the News</h1>
<p style={{ color: "var(--muted)", fontSize: 13 }}>
A running index of {total.toLocaleString()} articles about 90210 — original
and reboot. Each headline links to the original publisher; we don't
reproduce article text.
</p>
<NewsControls sort={sort} />
{news.length === 0 ? (
<p style={{ color: "var(--maroon)" }}>No news items yet.</p>
) : (
<div
style={{
display: "grid",
gap: 10,
marginTop: 8,
gridTemplateColumns: "repeat(var(--news-cols, 1), minmax(0, 1fr))",
}}
>
{news.map((n) => (
<a
key={n.id}
className="card"
href={n.canonical_url}
target="_blank"
rel="noopener noreferrer"
style={{ display: "block", color: "inherit" }}
>
<strong style={{ fontFamily: "var(--font-display)", fontSize: 16, lineHeight: 1.3 }}>
{n.headline}
</strong>
{n.summary_original ? (
<div style={{ color: "var(--ink-soft)", fontSize: 13, marginTop: 4 }}>
{n.summary_original}
</div>
) : null}
<div style={{ fontSize: 12, color: "var(--muted)", marginTop: 6 }}>
{n.publisher_name ?? "Source"}
{n.published_at
? ` · ${new Date(n.published_at).toLocaleDateString(undefined, {
year: "numeric",
month: "short",
day: "numeric",
})}`
: ""}
{" "}↗
</div>
</a>
))}
</div>
)}
{pages > 1 && (
<div style={{ display: "flex", gap: 12, marginTop: 20, alignItems: "center" }}>
{p > 1 ? <a href={q(p - 1)}>← Prev</a> : <span />}
<span style={{ fontSize: 13, color: "var(--muted)" }}>
Page {p} of {pages}
</span>
{p < pages ? <a href={q(p + 1)}>Next →</a> : <span />}
</div>
)}
</section>
);
}