"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import Link from "next/link"; import { useRouter, useSearchParams } from "next/navigation"; import { AlertCircle, CheckCircle2, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; const API_ROUTE = "/api/auth/consume-magic-link"; const SESSION_DURATION_MINUTES = Number( process.env.NEXT_PUBLIC_STYTCH_SESSION_DURATION_MINUTES ?? "60" ) || 60; const DEFAULT_DESTINATION = "/dashboard"; type StatusState = { state: "verifying" | "success" | "error"; headline: string; message: string; }; const INITIAL_STATUS: StatusState = { state: "verifying", headline: "We're verifying your magic link", message: "Hang tight—this usually takes just a moment.", }; function extractErrorMessage(error: unknown): string { if (error && typeof error === "object") { const typed = error as any; if (typed.error_message) { return typed.error_message; } if (typed.message) { return typed.message; } } return "We couldn't verify that link. Please request a new magic link from the login page."; } export default function AuthenticateRedirectPage() { const router = useRouter(); const searchParams = useSearchParams(); const [status, setStatus] = useState(INITIAL_STATUS); const hasAttemptedAuthRef = useRef(false); const magicLinkToken = searchParams.get("stytch_token") || searchParams.get("token"); const tokenType = searchParams.get("stytch_token_type") || searchParams.get("token_type"); const returnTo = searchParams.get("returnTo")?.trim() || DEFAULT_DESTINATION; const redirectToDestination = useCallback(() => { router.push(returnTo); router.refresh(); }, [returnTo, router]); const exchangeMagicLink = useCallback(async () => { if (!magicLinkToken) { setStatus({ state: "error", headline: "Magic link is missing or invalid", message: "This sign-in link is missing its token. Please request a new magic link.", }); return; } hasAttemptedAuthRef.current = true; setStatus(INITIAL_STATUS); try { const response = await fetch(API_ROUTE, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ token: magicLinkToken, tokenType: tokenType || "magic_links", sessionDurationMinutes: SESSION_DURATION_MINUTES, }), }); const data = await response.json(); if (!response.ok) { throw new Error(data?.error || "Failed to verify magic link."); } if (!data?.memberAuthenticated) { setStatus({ state: "error", headline: "Additional verification required", message: "We need a bit more information to finish signing you in. Please continue from the login page.", }); return; } setStatus({ state: "success", headline: "Magic link verified", message: "You’re all set. Redirecting you to your workspace…", }); redirectToDestination(); } catch (error) { console.error("Magic link verification failed:", error); setStatus({ state: "error", headline: "We couldn’t verify your link", message: extractErrorMessage(error), }); } }, [magicLinkToken, tokenType, redirectToDestination]); useEffect(() => { if (hasAttemptedAuthRef.current) return; void exchangeMagicLink(); }, [exchangeMagicLink]); const icon = status.state === "success" ? (