← back to Nineoh Guide

apps/web/app/cast/page.tsx

83 lines

import { pool } from "@/lib/db";
import { RightsImage } from "@/components/RightsImage";
import { castPath, type Asset } from "@nineoh/core";

function toAsset(r: any): Asset | null {
  if (!r.file_url) return null;
  return {
    id: r.id,
    type: "image",
    fileUrl: r.file_url,
    thumbnailUrl: r.file_url,
    licenseType: r.license_type,
    licenseUrl: r.license_url ?? null,
    attributionText: r.attribution_text ?? null,
    usageScope: "editorial",
    approvedForMarketing: false,
    licensePurchaseStatus: "none",
  };
}

export const revalidate = 3600; // ISR: cached HTML, hourly background refresh
export const metadata = { title: "Cast" };

async function getCast() {
  try {
    const { rows } = await pool.query(
      `select cp.id, cp.name, cp.biography_original,
              ch.name as character_name,
              a.file_url, a.license_type, a.license_url, a.attribution_text
         from cast_people cp
         left join credits cr on cr.person_id = cp.id and cr.credit_type = 'main-cast'
         left join characters ch on ch.id = cr.character_id
         left join assets a on a.id = cp.headshot_asset_id
        order by cp.name`
    );
    return rows;
  } catch {
    return [];
  }
}

export default async function Cast() {
  const cast = await getCast();
  return (
    <section>
      <h1>Cast</h1>
      {cast.length === 0 ? (
        <p style={{ color: "#8a1c1c" }}>
          Cast profiles are being written. Bios will be original; headshots will
          appear only once their image licenses are cleared.
        </p>
      ) : (
        <div
          style={{
            display: "grid",
            gap: 14,
            gridTemplateColumns: "repeat(auto-fill, minmax(300px, 1fr))",
          }}
        >
          {cast.map((p) => (
            <a
              key={p.id}
              className="card"
              href={castPath(p.name)}
              style={{ display: "block", color: "inherit" }}
            >
              <strong style={{ fontFamily: "var(--font-display)", fontSize: 16 }}>
                {p.name}
              </strong>
              {p.character_name ? (
                <div className="se" style={{ fontSize: 13 }}>as {p.character_name}</div>
              ) : null}
              <p style={{ margin: "5px 0 0", color: "var(--ink-soft)", fontSize: 12.5, lineHeight: 1.5 }}>
                {p.biography_original ?? "Bio coming soon."}
              </p>
            </a>
          ))}
        </div>
      )}
    </section>
  );
}