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