90 lines
4.0 KiB
TypeScript
90 lines
4.0 KiB
TypeScript
import { prisma } from "@/lib/prisma";
|
|
import Link from "next/link";
|
|
import { appTitle } from "@/lib/public-env";
|
|
|
|
export const metadata = {
|
|
title: `Dollars raised — ${appTitle()}`,
|
|
description: "Live totals from confirmed Stripe donations in this deployment.",
|
|
};
|
|
|
|
export default async function RaisedPage() {
|
|
const [agg, donorRows] = await Promise.all([
|
|
prisma.donation.aggregate({
|
|
_sum: { amountUsdCents: true },
|
|
_count: true,
|
|
}),
|
|
prisma.donation.groupBy({
|
|
by: ["userId"],
|
|
_count: true,
|
|
}),
|
|
]);
|
|
|
|
const raisedUsd = (agg._sum.amountUsdCents ?? 0) / 100;
|
|
const donationCount = agg._count;
|
|
const uniqueDonors = donorRows.length;
|
|
const goalUsd = parseFloat(process.env.PUBLIC_CAMPAIGN_GOAL_USD ?? "250000");
|
|
const pct = goalUsd > 0 ? Math.min(100, Math.round((raisedUsd / goalUsd) * 100)) : 0;
|
|
|
|
const formatted = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(raisedUsd);
|
|
|
|
return (
|
|
<main className="min-h-[70vh] border-b border-white/10 py-16">
|
|
<div className="mx-auto max-w-3xl px-4 sm:px-6">
|
|
<p className="text-xs uppercase tracking-[0.32em] text-sky-200/80">Transparency</p>
|
|
<h1 className="mt-4 text-4xl font-semibold text-white sm:text-5xl">Total dollars raised</h1>
|
|
<p className="mt-4 text-lg text-slate-400">
|
|
Sum of successful donations processed through Stripe for this app ({appTitle()}). Updates as webhooks confirm
|
|
payments.
|
|
</p>
|
|
|
|
<div className="mt-12 rounded-[28px] border border-white/10 bg-gradient-to-br from-sky-500/15 via-indigo-900/40 to-fuchsia-900/30 p-8 shadow-[0_0_100px_rgba(56,189,248,0.12)]">
|
|
<p className="text-sm uppercase tracking-[0.2em] text-slate-400">Confirmed via Stripe</p>
|
|
<p className="mt-4 font-mono text-5xl font-semibold tracking-tight text-white sm:text-6xl">{formatted}</p>
|
|
<p className="mt-6 flex flex-wrap gap-6 text-sm text-slate-300">
|
|
<span>
|
|
<strong className="text-white">{donationCount}</strong> donation{donationCount === 1 ? "" : "s"}
|
|
</span>
|
|
<span>
|
|
<strong className="text-white">{uniqueDonors}</strong> supporter{uniqueDonors === 1 ? "" : "s"}
|
|
</span>
|
|
</p>
|
|
</div>
|
|
|
|
<div className="mt-10">
|
|
<div className="flex justify-between text-xs text-slate-500">
|
|
<span>$0</span>
|
|
<span>
|
|
Goal {new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 }).format(goalUsd)}{" "}
|
|
<span className="text-slate-600">(PUBLIC_CAMPAIGN_GOAL_USD)</span>
|
|
</span>
|
|
</div>
|
|
<div className="mt-2 h-3 overflow-hidden rounded-full border border-white/10 bg-black/40">
|
|
<div
|
|
className="h-full rounded-full bg-gradient-to-r from-sky-400 via-indigo-400 to-fuchsia-400 transition-[width]"
|
|
style={{ width: `${pct}%` }}
|
|
/>
|
|
</div>
|
|
<p className="mt-2 text-center text-xs text-slate-500">{pct}% of demo goal</p>
|
|
</div>
|
|
|
|
<div className="mt-12 rounded-2xl border border-white/10 bg-white/5 p-6 text-sm text-slate-400">
|
|
<p className="font-medium text-white">Note</p>
|
|
<p className="mt-2 leading-relaxed">
|
|
This total reflects <code className="rounded bg-black/30 px-1">Donation</code> rows created by the Stripe webhook
|
|
only—only processed charges count. Configure committee reporting separately for compliance.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="mt-10 flex flex-wrap gap-4">
|
|
<Link href="/#donate" className="rounded-full bg-gradient-to-r from-sky-500 to-indigo-500 px-6 py-3 text-sm font-semibold text-white">
|
|
Donate
|
|
</Link>
|
|
<Link href="/" className="rounded-full border border-white/15 px-6 py-3 text-sm font-semibold text-white hover:bg-white/5">
|
|
Back home
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|