Files
EpicNext-Cms/src/app/(site)/verify/page.tsx
T
openhands 7a41775c7e fix: disable route prefetching app-wide to avoid spurious requests
Wrap next/link in a shared Link component that ships prefetch=false by
default, so no route is ever prefetched (viewport or hover) anymore, and
drop the DNS prefetch hint. Removes hidden background requests that were
the source of intermittent issues.
2026-08-29 19:27:13 +02:00

205 lines
5.0 KiB
TypeScript

import { eq } from "drizzle-orm";
import { CheckCircle2, Clock, MailX } from "lucide-react";
import { getTranslations } from "next-intl/server";
import { isValidVerificationToken } from "@/actions/email-verify";
import Link from "@/components/link";
import { SurfaceCard } from "@/components/surface-card";
import { db, User } from "@/lib/db";
type Status = "verified" | "already" | "invalid" | "unavailable";
function StatusCard({
icon,
title,
subtitle,
children,
color,
}: {
icon: React.ReactNode;
title: string;
subtitle: string;
children: React.ReactNode;
color: string;
}) {
const tintMap: Record<string, string> = {
green: "#16a34a",
yellow: "#d97706",
red: "#dc2626",
blue: "#2563eb",
};
const tint = tintMap[color] ?? tintMap.blue;
return (
<main className="flex min-h-[60vh] items-center justify-center px-4">
<SurfaceCard className="w-full max-w-md">
<div
className="flex items-center gap-2.5 border-b px-5 py-4"
style={{
background: `color-mix(in srgb, ${tint} 12%, var(--color-surface))`,
borderColor:
"color-mix(in srgb, var(--color-text-muted) 10%, transparent)",
}}
>
<span
className="flex h-7 w-7 items-center justify-center rounded-lg"
style={{
background: `color-mix(in srgb, ${tint} 18%, transparent)`,
color: tint,
}}
>
{icon}
</span>
<h1
className="text-sm font-bold tracking-tight"
style={{ color: "var(--color-text-readable)" }}
>
{title}
</h1>
</div>
<div className="flex flex-col items-center gap-4 px-6 py-8 text-center">
<div
className="flex h-16 w-16 items-center justify-center rounded-full"
style={{
background: `color-mix(in srgb, ${tint} 12%, transparent)`,
color: tint,
}}
>
{icon}
</div>
<p
className="text-sm font-semibold"
style={{ color: "var(--color-text-muted)" }}
>
{subtitle}
</p>
{children}
</div>
</SurfaceCard>
</main>
);
}
export default async function VerifyPage({
searchParams,
}: {
searchParams: Promise<{
token?: string;
email?: string;
}>;
}) {
const t = await getTranslations("pages.verify");
const { token = "", email = "" } = await searchParams;
const normalisedEmail = email.trim().toLowerCase();
let status: Status = "invalid";
if (normalisedEmail && token) {
const ok = await isValidVerificationToken(normalisedEmail, token);
if (ok) {
try {
const [user] = await db
.select({ id: User.id, mailVerified: User.mailVerified })
.from(User)
.where(eq(User.mail, normalisedEmail))
.limit(1);
if (!user) {
status = "invalid";
} else if (user.mailVerified === "1") {
status = "already";
} else {
await db
.update(User)
.set({ mailVerified: "1" })
.where(eq(User.id, user.id));
status = "verified";
}
} catch {
status = "unavailable";
}
}
}
const linkClass =
"btn-shine inline-flex items-center gap-2 font-extrabold text-sm px-6 py-3 rounded-xl transition-all duration-200 hover:scale-[1.02] active:scale-95 shadow-lg";
const linkStyle = {
background: "var(--color-primary)",
color: "var(--color-primary-foreground)",
boxShadow:
"0 4px 20px color-mix(in srgb, var(--color-primary) 30%, transparent)",
};
return (
<>
{status === "verified" && (
<StatusCard
icon={<CheckCircle2 size={22} />}
title={t("verifiedTitle")}
subtitle={t("verifiedSubtitle")}
color="green"
>
<p className="text-sm" style={{ color: "var(--color-text-muted)" }}>
{t("verifiedBody")}
</p>
<Link href="/login" className={linkClass} style={linkStyle}>
{t("goToLogin")}
</Link>
</StatusCard>
)}
{status === "already" && (
<StatusCard
icon={<CheckCircle2 size={22} />}
title={t("alreadyTitle")}
subtitle={t("alreadySubtitle")}
color="yellow"
>
<p className="text-sm" style={{ color: "var(--color-text-muted)" }}>
{t("alreadyBody")}
</p>
<Link href="/login" className={linkClass} style={linkStyle}>
{t("goToLogin")}
</Link>
</StatusCard>
)}
{status === "unavailable" && (
<StatusCard
icon={<Clock size={22} />}
title={t("unavailableTitle")}
subtitle={t("unavailableSubtitle")}
color="blue"
>
<p className="text-sm" style={{ color: "var(--color-text-muted)" }}>
{t("unavailableBody")}
</p>
</StatusCard>
)}
{status === "invalid" && (
<StatusCard
icon={<MailX size={22} />}
title={t("invalidTitle")}
subtitle={t("invalidSubtitle")}
color="red"
>
<p className="text-sm" style={{ color: "var(--color-text-muted)" }}>
{t("invalidBody")}
</p>
<Link
href="/login"
className={`${linkClass} !shadow-none`}
style={{
background: "transparent",
color: "var(--color-text-readable)",
border:
"2px solid color-mix(in srgb, var(--color-text-muted) 20%, transparent)",
}}
>
{t("backToLogin")}
</Link>
</StatusCard>
)}
</>
);
}