← back to Govarbitrage
src/app/pricing/page.tsx
119 lines
import type { Metadata } from "next";
import { TIERS, TIER_ORDER } from "@/lib/tiers";
import { getCurrentTier } from "@/lib/current-user";
import { STRIPE_MODE } from "@/lib/billing";
import UpgradeButton from "./UpgradeButton";
export const dynamic = "force-dynamic";
export const metadata: Metadata = {
title: "Pricing",
description:
"GovArbitrage plans: Free (delayed deals), Standard ($12/mo — real-time + hot-deal alerts), and Premium ($29/mo — unlimited, confidence bands, priority alerts). Cancel anytime.",
alternates: { canonical: "/pricing" },
openGraph: {
title: "Pricing — GovArbitrage",
description:
"GovArbitrage plans: Free (delayed deals), Standard ($12/mo — real-time + hot-deal alerts), and Premium ($29/mo — unlimited, confidence bands, priority alerts). Cancel anytime.",
url: "/pricing",
type: "website",
},
};
const pricingJsonLd = {
"@context": "https://schema.org",
"@type": "ItemList",
name: "GovArbitrage Pricing Plans",
itemListElement: TIER_ORDER.map((key, idx) => {
const t = TIERS[key];
return {
"@type": "ListItem",
position: idx + 1,
item: {
"@type": "Offer",
name: t.label,
description: t.blurb,
price: t.priceUsd.toString(),
priceCurrency: "USD",
eligibleDuration: {
"@type": "QuantitativeValue",
value: 1,
unitCode: "MON",
},
},
};
}),
};
export default async function PricingPage() {
const current = await getCurrentTier();
return (
<main className="mx-auto max-w-4xl space-y-6 p-5 pt-14 text-foreground">
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(pricingJsonLd) }}
/>
<div className="text-center space-y-2">
<h1 className="text-3xl font-semibold tracking-tight">Find the deals before everyone else</h1>
<p className="text-base text-muted-foreground">
8 government-surplus auction networks, de-duped and scored — with honest,
confidence-labeled max-bid math. Cancel anytime.
</p>
{STRIPE_MODE !== "test" && (
<span className="inline-block bg-warning/20 text-warning text-xs px-3 py-1 rounded-full font-medium">
TEST MODE — no real charges (checkout is simulated)
</span>
)}
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-3 mt-7">
{TIER_ORDER.map((key) => {
const t = TIERS[key];
const isCurrent = current === key;
const featured = key === "STANDARD";
return (
<div
key={key}
className={`relative rounded-xl bg-card text-card-foreground p-5 ${
featured ? "border-2 border-foreground" : "border"
}`}
>
{featured && (
<div className="absolute -top-3 left-1/2 -translate-x-1/2 bg-foreground text-background text-[11px] font-bold px-3 py-0.5 rounded-full">
MOST POPULAR
</div>
)}
<div className="font-bold text-lg">{t.label}</div>
<div className="my-1.5">
<span className="text-4xl font-extrabold">${t.priceUsd}</span>
<span className="text-muted-foreground">{t.priceUsd > 0 ? "/mo" : ""}</span>
</div>
<div className="text-muted-foreground text-sm min-h-9">{t.blurb}</div>
<ul className="list-none p-0 my-3.5 space-y-1 text-sm leading-relaxed">
{t.features.map((f) => (
<li key={f}>
<span className="text-positive mr-1.5">✓</span>{f}
</li>
))}
</ul>
{t.priceUsd === 0 ? (
<div className={`py-2.5 font-bold ${isCurrent ? "text-positive" : "text-muted-foreground"}`}>
{isCurrent ? "✓ Your plan" : "Default plan"}
</div>
) : (
<UpgradeButton tier={key as "STANDARD" | "PREMIUM"} label={t.label} current={isCurrent} />
)}
</div>
);
})}
</div>
<p className="text-center text-muted-foreground text-xs leading-relaxed">
Estimates are heuristic and confidence-labeled — always verify comps before bidding.
Prices shown are introductory and may change.
</p>
</main>
);
}