brackt/app/routes/check-email.tsx
Chris Parsons c8748d1b14
Add email verification for email/password signups (#400)
New email/password accounts must verify before signing in. Social login
users (Google, Discord) are unaffected. Existing users are grandfathered
via an idempotent migration. Adds a /check-email interstitial with a
resend button, and a --success CSS token for consistent styling.

Closes #343

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 23:58:45 -07:00

89 lines
3 KiB
TypeScript

import { useState } from "react";
import { Link, redirect, useLoaderData } from "react-router";
import { authClient } from "~/lib/auth-client";
import { Button } from "~/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "~/components/ui/card";
import type { Route } from "./+types/check-email";
export function meta(): Route.MetaDescriptors {
return [{ title: "Check Your Email - Brackt" }];
}
export function safeRedirectTo(value: string | null): string {
if (!value) return "/";
return value.startsWith("/") && !value.startsWith("//") ? value : "/";
}
export async function loader(args: Route.LoaderArgs) {
const url = new URL(args.request.url);
const email = url.searchParams.get("email");
if (!email) return redirect("/register");
return {
email,
redirectTo: safeRedirectTo(url.searchParams.get("redirectTo")),
};
}
type ResendState = "idle" | "sending" | "sent" | "error";
export default function CheckEmailPage() {
const { email, redirectTo } = useLoaderData<typeof loader>();
const [resendState, setResendState] = useState<ResendState>("idle");
const [resendError, setResendError] = useState<string | null>(null);
async function handleResend() {
setResendState("sending");
setResendError(null);
const { error } = await authClient.sendVerificationEmail({
email,
callbackURL: redirectTo,
});
if (error) {
setResendState("error");
setResendError(error.message ?? "Could not resend. Please try again.");
} else {
setResendState("sent");
}
}
return (
<div className="min-h-screen flex items-center justify-center p-4">
<Card className="w-full max-w-md">
<CardHeader className="space-y-1">
<CardTitle className="text-2xl">Check your email</CardTitle>
<CardDescription>We sent a verification link to {email}</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-sm text-muted-foreground">
Click the link in the email to verify your account and get started. If you
don&apos;t see it, check your spam folder.
</p>
<div className="space-y-2">
<Button
variant="outline"
className="w-full"
onClick={handleResend}
disabled={resendState === "sending"}
>
{resendState === "sending" ? "Sending…" : "Resend verification email"}
</Button>
{resendState === "sent" && (
<p className="text-sm text-success">Email sent! Check your inbox.</p>
)}
{resendState === "error" && resendError && (
<p className="text-sm text-destructive">{resendError}</p>
)}
</div>
<p className="text-center text-sm text-muted-foreground">
Wrong account?{" "}
<Link to="/login" className="underline">
Sign in
</Link>
</p>
</CardContent>
</Card>
</div>
);
}