"use client"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import Link from "next/link"; import { useStytchMember } from "@stytch/nextjs/b2b"; import { ArrowRight, CheckCircle2, Home, Inbox, Mail } from "lucide-react"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; const highlights = [ "Single workspace to review invoices, approvals, and exports.", "Ready-made controls that plug into your existing banking stack.", "Sessions scoped to your organization with role-aware access.", ]; const emailProviders = [ { label: "Open Gmail", href: "https://mail.google.com/", }, { label: "Open Outlook", href: "https://outlook.office.com/mail/", }, { label: "Open iCloud Mail", href: "https://www.icloud.com/mail", }, { label: "Open Yahoo Mail", href: "https://mail.yahoo.com/", }, ]; export default function AuthPage() { const router = useRouter(); const searchParams = useSearchParams(); const { member, isInitialized } = useStytchMember(); const [email, setEmail] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); const [status, setStatus] = useState<{ type: "info" | "error" | "success"; message: string; } | null>(null); const [isRedirecting, setIsRedirecting] = useState(false); const [view, setView] = useState<"form" | "success">("form"); const [lastSubmittedEmail, setLastSubmittedEmail] = useState(""); const hasRedirectedRef = useRef(false); const redirectTimeoutRef = useRef(null); const targetAfterLogin = useMemo(() => { const returnTo = searchParams.get("returnTo") || "/dashboard"; return returnTo.startsWith("/") && !returnTo.startsWith("//") ? returnTo : "/dashboard"; }, [searchParams]); const handleAuthSuccess = useCallback(() => { if (hasRedirectedRef.current) return; hasRedirectedRef.current = true; setIsRedirecting(true); setStatus({ type: "info", message: "You’re signed in. Redirecting to your workspace…", }); router.replace(targetAfterLogin); setTimeout(() => { router.refresh(); }, 150); if (typeof window !== "undefined") { if (redirectTimeoutRef.current !== null) { window.clearTimeout(redirectTimeoutRef.current); } redirectTimeoutRef.current = window.setTimeout(() => { window.location.assign(targetAfterLogin); }, 1500); } }, [router, targetAfterLogin]); useEffect(() => { if (!isInitialized) return; if (member) { handleAuthSuccess(); } }, [isInitialized, member, handleAuthSuccess]); useEffect(() => { const hasMagicLinkParams = searchParams.has("stytch_token") || searchParams.has("token") || searchParams.has("stytch_token_type"); if (hasMagicLinkParams) { setStatus({ type: "info", message: "We’re verifying your sign-in link. This usually takes just a moment.", }); } }, [searchParams]); const submitEmail = useCallback( async ( rawEmail: string, options: { resetField?: boolean; stayOnSuccessView?: boolean } = {} ) => { const { resetField = true, stayOnSuccessView = false } = options; const trimmedEmail = rawEmail.trim().toLowerCase(); if (!trimmedEmail) { setStatus({ type: "error", message: "Please enter a valid email address.", }); return; } setIsSubmitting(true); setStatus({ type: "info", message: "Checking your workspace access…", }); if (!stayOnSuccessView) { setView("form"); } try { const query = new URLSearchParams({ email: trimmedEmail }).toString(); const apiBaseUrl = (process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8080/api").replace(/\/$/, ""); const emailCheckResponse = await fetch(`${apiBaseUrl}/auth/check-email?${query}`); if (emailCheckResponse.status === 404) { const errorBody = await emailCheckResponse.json().catch(() => null); setStatus({ type: "error", message: (errorBody && errorBody.message) || "We couldn't find an account with that email. Try a different email or ask your admin to invite you.", }); return; } if (!emailCheckResponse.ok) { const errorBody = await emailCheckResponse.json().catch(() => null); throw new Error( (errorBody && errorBody.message) || "We couldn't verify that email right now. Please try again in a moment.", ); } setStatus({ type: "info", message: "Sending your secure sign-in link…", }); const response = await fetch("/api/auth/magic-link", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ email: trimmedEmail }), }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || "Failed to send sign-in link."); } setLastSubmittedEmail(trimmedEmail); setStatus({ type: "success", message: "Check your email for a secure link to sign in.", }); setView("success"); if (resetField) { setEmail(""); } } catch (error: any) { console.error("Sign-in link send error:", error); setStatus({ type: "error", message: error?.message || "Something went wrong while sending your sign-in link. Please try again.", }); } finally { setIsSubmitting(false); } }, [] ); const handleSendMagicLink = async (e: React.FormEvent) => { e.preventDefault(); await submitEmail(email, { resetField: true }); }; const handleResend = async () => { if (!lastSubmittedEmail) return; await submitEmail(lastSubmittedEmail, { resetField: false, stayOnSuccessView: true, }); }; useEffect(() => { router.prefetch(targetAfterLogin); }, [router, targetAfterLogin]); useEffect(() => { return () => { if (typeof window !== "undefined" && redirectTimeoutRef.current !== null) { window.clearTimeout(redirectTimeoutRef.current); redirectTimeoutRef.current = null; } }; }, []); if (!isInitialized) { return (

Checking your workspace session…

); } if (isRedirecting || member) { return (

Redirecting to your dashboard

{(status && status.message) || "We’re setting up your workspace now."}

Taking longer than expected?{" "} Open your workspace .

); } return (
Secure email sign-in Home

Welcome back to Your App

Use your work email to receive a one-time, organization-aware sign-in link. We’ll land you back where you left off as soon as you’re authenticated.

{highlights.map((item) => (

{item}

))}

Need a hand?

Reach out at{" "} support@yourapp.com {" "} for support, or check the documentation in your workspace.

); }