← back to Govarbitrage

src/app/deals/[date]/page.tsx

197 lines

import type { Metadata } from "next";
import Link from "next/link";
import { notFound } from "next/navigation";
import { getDigestEditionsForDate, type DigestEdition, type SnapshotDeal } from "@/lib/digest-snapshot";
import { publicBaseUrl } from "@/lib/site";

export const dynamic = "force-dynamic";

const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;

const fmtDate = (iso: string) =>
  new Date(`${iso}T12:00:00Z`).toLocaleDateString("en-US", {
    year: "numeric",
    month: "long",
    day: "numeric",
    timeZone: "UTC",
  });

const confidenceLabel: Record<string, { text: string; color: string }> = {
  HIGH: { text: "High confidence", color: "#16a34a" },
  MEDIUM: { text: "Medium confidence", color: "#d97706" },
  LOW: { text: "Low confidence", color: "#64748b" },
};

// The public teaser shows a conservative ROI BAND, not a point estimate —
// roiConservative is already the haircut low band, so band = [~80% of it, it].
function roiBand(d: SnapshotDeal): string {
  const hi = Math.round(d.roiConservative * 100);
  const lo = Math.max(0, Math.round(hi * 0.8));
  return `${lo}–${hi}%${d.roiCapped ? " (capped)" : ""}`;
}

type Params = { date: string };

export async function generateMetadata({ params }: { params: Promise<Params> }): Promise<Metadata> {
  const { date } = await params;
  if (!DATE_RE.test(date)) return {};
  const editions = await getDigestEditionsForDate(date);
  if (editions.length === 0) return {};

  const base = publicBaseUrl();
  const dealCount = editions.reduce((n, e) => n + e.dealCount, 0);
  const top = editions.flatMap((e) => e.deals)[0];
  const title = `Top-10 Gov-Surplus Deals — ${fmtDate(date)}`;
  const description = top
    ? `${dealCount} honesty-gated government-surplus deal${dealCount === 1 ? "" : "s"} on ${fmtDate(date)}, led by "${top.title}". Conservative, confidence-labeled numbers only.`
    : `The ${fmtDate(date)} digest ran with no deals clearing the hot-deal gate — we publish nothing rather than pad the list.`;

  return {
    title: `${title} | GovArbitrage`,
    description,
    alternates: { canonical: `${base}/deals/${date}` },
    openGraph: {
      title,
      description,
      url: `${base}/deals/${date}`,
      type: "article",
    },
    twitter: { card: "summary_large_image" },
  };
}

function editionJsonLd(edition: DigestEdition, base: string) {
  return {
    "@context": "https://schema.org",
    "@type": "ItemList",
    name: `Top-10 Gov-Surplus Deals — ${fmtDate(edition.date)} (${edition.slot === "AM" ? "Morning" : "Evening"} edition)`,
    url: `${base}/deals/${edition.date}`,
    numberOfItems: edition.deals.length,
    itemListOrder: "https://schema.org/ItemListOrderAscending",
    itemListElement: edition.deals.map((d) => ({
      "@type": "ListItem",
      position: d.rank,
      name: d.title,
      url: `${base}/deals/${edition.date}`,
      description: d.disclaimer || `Government-surplus deal ranked #${d.rank} in the ${edition.slot === "AM" ? "morning" : "evening"} edition. Conservative ROI: ${Math.round(d.roiConservative * 100)}%.`,
    })),
  };
}

function TeaserRow({ deal }: { deal: SnapshotDeal }) {
  const where = [deal.locationCity, deal.locationState].filter(Boolean).join(", ");
  const conf = confidenceLabel[deal.confidence] ?? confidenceLabel.LOW;
  return (
    <li style={{ border: "1px solid #e2e8f0", borderRadius: 10, padding: "14px 18px", marginBottom: 12 }}>
      <div style={{ display: "flex", gap: 12, alignItems: "baseline" }}>
        <span style={{ fontWeight: 700, color: "#94a3b8", fontSize: 15, flexShrink: 0 }}>#{deal.rank}</span>
        <div style={{ minWidth: 0 }}>
          <div style={{ fontWeight: 700, fontSize: 16 }}>{deal.title}</div>
          <div style={{ color: "#64748b", fontSize: 13, marginTop: 2 }}>
            {deal.source.replace(/_/g, " ")}
            {where ? <> · {where}</> : null}
            {" · "}
            <span style={{ color: conf.color, fontWeight: 600 }}>{conf.text}</span>
          </div>
          <div style={{ fontSize: 14, marginTop: 8, color: "#334155" }}>
            Conservative ROI band: <b>{roiBand(deal)}</b>
          </div>
          <div style={{ fontSize: 14, marginTop: 6, color: "#334155", display: "flex", flexWrap: "wrap", gap: "4px 14px", alignItems: "center" }}>
            <span>
              Rec. max bid:{" "}
              <span aria-hidden style={{ filter: "blur(5px)", userSelect: "none" }}>$8,450</span>
            </span>
            <span>
              Est. net profit:{" "}
              <span aria-hidden style={{ filter: "blur(5px)", userSelect: "none" }}>$3,120</span>
            </span>
            <a href="/newsletter" style={{ color: "#2563eb", fontSize: 13, fontWeight: 600 }}>
              Subscribe free to see the full math →
            </a>
          </div>
          <div style={{ color: "#94a3b8", fontSize: 12, marginTop: 6 }}>{deal.disclaimer}</div>
        </div>
      </div>
    </li>
  );
}

export default async function DealsByDatePage({ params }: { params: Promise<Params> }) {
  const { date } = await params;
  if (!DATE_RE.test(date)) notFound();

  const editions = await getDigestEditionsForDate(date);
  if (editions.length === 0) notFound();

  const base = publicBaseUrl();

  return (
    <main style={{ maxWidth: 720, margin: "56px auto", fontFamily: "system-ui", padding: "0 20px", color: "#0f172a" }}>
      {editions.map((e) => (
        <script
          key={`ld-${e.slot}`}
          type="application/ld+json"
          dangerouslySetInnerHTML={{ __html: JSON.stringify(editionJsonLd(e, base)) }}
        />
      ))}

      <p style={{ fontSize: 13, marginBottom: 8 }}>
        <Link href="/deals" style={{ color: "#64748b", textDecoration: "none" }}>← Deal archive</Link>
      </p>
      <header style={{ marginBottom: 24 }}>
        <h1 style={{ fontSize: 30, margin: 0 }}>Top-10 Gov-Surplus Deals — {fmtDate(date)}</h1>
        <p style={{ color: "#475569", fontSize: 15, lineHeight: 1.6 }}>
          Frozen as published. Every figure is honesty-gated — confidence-discounted,
          capped, and disclaimed. Exact recommended max bids and net-profit math go
          to <Link href="/newsletter" style={{ color: "#2563eb" }}>free subscribers</Link>.
        </p>
      </header>

      {editions.map((e) => (
        <section key={e.slot} style={{ marginBottom: 30 }}>
          <h2 style={{ fontSize: 20, margin: "0 0 4px" }}>
            {e.slot === "AM" ? "Morning" : "Evening"} edition
            <span style={{ color: "#94a3b8", fontWeight: 400, fontSize: 14 }}>
              {" "}· {e.dealCount} deal{e.dealCount === 1 ? "" : "s"}
            </span>
          </h2>
          {e.deals.length === 0 ? (
            <p style={{ color: "#64748b", fontSize: 14, border: "1px dashed #cbd5e1", borderRadius: 10, padding: "18px" }}>
              No deals cleared the hot-deal gate this run. We&apos;d rather publish
              nothing than pad the list with marginal lots.
            </p>
          ) : (
            <ul style={{ listStyle: "none", padding: 0, margin: "12px 0 0" }}>
              {e.deals.map((d) => (
                <TeaserRow key={d.rank} deal={d} />
              ))}
            </ul>
          )}
        </section>
      ))}

      <div style={{ textAlign: "center", margin: "34px 0" }}>
        <a
          href="/newsletter"
          style={{
            display: "inline-block",
            background: "#0f172a",
            color: "#fff",
            padding: "12px 26px",
            borderRadius: 8,
            fontSize: 15,
            textDecoration: "none",
          }}
        >
          Get the next Top-10 by email — subscribe free →
        </a>
      </div>

      <p style={{ textAlign: "center", color: "#94a3b8", fontSize: 12, lineHeight: 1.6 }}>
        Estimates are heuristic — always verify comps before bidding. Archive pages
        are frozen at publish time; live auction state will have moved on.
      </p>
    </main>
  );
}