← back to Trademarks Copyright
src/app/drops/view/[token]/page.tsx
87 lines
import Link from "next/link";
import { notFound } from "next/navigation";
import { query } from "@/lib/db";
import ReferralWidget from "@/components/ReferralWidget";
type Params = { token: string };
export default async function SubscriberPortal({ params }: { params: Promise<Params> }) {
const { token } = await params;
const { rows: subs } = await query<{
id: number; email: string; name: string | null; tier: string; status: string;
trial_drops_left: number; signed_up_at: string; last_delivered_at: string | null;
}>(
`SELECT id, email, name, tier, status, trial_drops_left, signed_up_at, last_delivered_at
FROM subscribers WHERE token = $1`, [token]
);
if (!subs.length) notFound();
const sub = subs[0];
const { rows: drops } = await query<{
id: number; drop_date: string; subject: string;
sent_at: string | null; intro: string | null;
}>(
`SELECT d.id, d.drop_date, d.subject, d.sent_at, d.intro
FROM drops d
JOIN deliveries dv ON dv.drop_id = d.id
WHERE dv.subscriber_id = $1
ORDER BY d.drop_date DESC`, [sub.id]
);
const { rows: refStats } = await query<{ total: string; rewarded: string }>(
`SELECT COUNT(*)::text AS total,
COUNT(*) FILTER (WHERE reward_given = TRUE)::text AS rewarded
FROM referrals WHERE referrer_token = $1`,
[token]
);
const totalRefs = Number(refStats[0]?.total ?? 0);
const rewardedRefs = Number(refStats[0]?.rewarded ?? 0);
const untilNext = 3 - (totalRefs - rewardedRefs * 3);
return (
<div>
<div className="mb-4 card p-5">
<div className="text-xs uppercase tracking-widest text-brass">Your portal</div>
<h1 className="mt-1 text-3xl font-semibold">{sub.name || sub.email}</h1>
<div className="mt-2 flex flex-wrap items-center gap-3 text-sm">
<span className="tag">Tier: {sub.tier}</span>
<span className="tag">Status: {sub.status}</span>
{sub.tier === "trial" && <span className="tag">{sub.trial_drops_left} trial drops left</span>}
<span className="text-ink/60">Signed up {new Date(sub.signed_up_at).toLocaleDateString()}</span>
<span className="ml-auto text-xs">
<Link href={`/drops/unsubscribe/${token}`} className="text-ink/50 hover:text-red-600">Unsubscribe</Link>
</span>
</div>
</div>
<ReferralWidget
token={token}
totalRefs={totalRefs}
untilNext={Math.max(0, untilNext)}
rewardedRefs={rewardedRefs}
/>
<h2 className="text-xl font-semibold mt-6">Drop archive</h2>
{drops.length === 0 ? (
<div className="mt-3 text-ink/60">No drops delivered yet. Your first arrives at 9am PT.</div>
) : (
<ul className="mt-3 space-y-2">
{drops.map((d) => (
<li key={d.id} className="card p-4">
<div className="text-xs text-brass">
{new Date(d.drop_date).toLocaleDateString(undefined, { weekday: "long", month: "long", day: "numeric" }).toUpperCase()}
</div>
<h3 className="font-semibold">{d.subject}</h3>
{d.intro && <p className="text-sm text-ink/70"><em>{d.intro}</em></p>}
<div className="mt-2">
<Link href={`/drops/view/${token}/${d.id}`} className="text-sm text-brass hover:underline">Read drop →</Link>
</div>
</li>
))}
</ul>
)}
</div>
);
}