← 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>
  );
}