import { Link, createFileRoute } from "@tanstack/react-router"; import { useEffect, useState } from "react"; import { toast } from "sonner"; import { AuthPageCard, AuthPageShell, authRedirectSearchSchema, } from "@/client/features/auth/AuthPage"; import { captureClientEvent } from "@/client/lib/posthog"; import { authClient, useSession } from "@/lib/auth-client"; import { isHostedClientAuthMode } from "@/lib/auth-mode"; import { getSignInSearch, normalizeAuthRedirect } from "@/lib/auth-redirect"; import { z } from "zod"; const verificationIssueSchema = z .enum(["invalid_token", "token_expired", "user_not_found", "unknown"]) .catch("unknown"); const verifyEmailSearchSchema = authRedirectSearchSchema.extend({ error: z.string().optional(), email: z.string().optional(), }); export const Route = createFileRoute("/verify-email")({ validateSearch: verifyEmailSearchSchema, component: VerifyEmailPage, }); function getVerificationErrorMessage(error: string | undefined) { switch ((error ?? "").toLowerCase()) { case "invalid_token": return "This link is no longer valid. Request a new email to keep going."; case "token_expired": return "This link has expired. Request a new email to keep going."; case "user_not_found": return "We couldn't find this account anymore. Try creating it again."; default: return error ? "We couldn't confirm this email. Request a new email and try again." : null; } } function getVerifyEmailPageCopy({ isHostedMode, errorMessage, isWaiting, isPending, isVerified, email, }: { isHostedMode: boolean; errorMessage: string | null; isWaiting: boolean; isPending: boolean; isVerified: boolean; email: string | undefined; }) { if (!isHostedMode) { return { title: "Verify email", helperText: "Email confirmation isn't available right now.", }; } if (errorMessage) { return { title: "We couldn't confirm your email", helperText: errorMessage, }; } if (isWaiting && email) { return { title: "Verify your email", helperText: `Click the link we sent to ${email} to verify your email.`, }; } if (isPending) { return { title: "Verify email", helperText: "Checking your email confirmation.", }; } if (isVerified) { return { title: "Email confirmed", helperText: "You're all set. Taking you to your account now.", }; } return { title: "Email confirmed", helperText: "Your email is confirmed. You can sign in now.", }; } function VerifyEmailPage() { const search = Route.useSearch(); const redirectTo = normalizeAuthRedirect(search.redirect); const isHostedMode = isHostedClientAuthMode(); const { data: session, isPending } = useSession(); const bypassEmailVerification = import.meta.env.BYPASS_EMAIL_VERIFICATION === "true"; const errorMessage = getVerificationErrorMessage(search.error); const verificationIssueType = search.error ? verificationIssueSchema.parse(search.error) : null; const email = search.email; const isWaiting = !errorMessage && !bypassEmailVerification && !session?.user?.emailVerified && !!email; const [isResending, setIsResending] = useState(false); const isVerified = !!session?.user?.emailVerified; const pageCopy = getVerifyEmailPageCopy({ isHostedMode, errorMessage, isWaiting, isPending, isVerified, email, }); useEffect(() => { if ( isPending || (!isVerified && !(bypassEmailVerification && session?.user?.id)) ) { return; } if (isVerified) { captureClientEvent("auth:verification_success", { redirect_to: redirectTo, }); } // Full page reload instead of client-side navigation: the auth→app // transition needs a clean server-side load so that all server function // handlers are freshly registered (client-side nav during Vite HMR can // hit the server before updated handlers are ready, causing // "action is not a function" errors). window.location.replace(redirectTo); }, [ bypassEmailVerification, isPending, isVerified, redirectTo, session?.user?.id, ]); useEffect(() => { if (!verificationIssueType) { return; } captureClientEvent("auth:verification_issue", { issue_type: verificationIssueType, }); }, [verificationIssueType]); async function handleResend() { if (!email) return; setIsResending(true); try { const callbackURL = new URL("/verify-email", window.location.origin); if (redirectTo !== "/") callbackURL.searchParams.set("redirect", redirectTo); const result = await authClient.sendVerificationEmail({ email, callbackURL: callbackURL.toString(), }); if (result.error) { toast.error(result.error.message || "We couldn't send another email."); return; } captureClientEvent("auth:verification_resend"); toast.success("A new email is on the way."); } catch { toast.error( "We couldn't send another email right now. Please try again.", ); } finally { setIsResending(false); } } return ( Back to sign in

} > {!isHostedMode ? null : errorMessage ? (
{errorMessage}
Back to sign in
) : isWaiting ? (
) : isPending ? (
) : isVerified ? (
) : ( Sign in to continue )}
); }