"use client"; /** * Unified login methods — same weight for each path into the dashboard: * 1. Email OTP (Briven) * 2. Konnos OAuth (Briven) * 3. MetaMask (browser wallet) * 4. mavi wallet (local encrypted vault — create at /portal/mavi) */ import { injected } from "@wagmi/connectors/injected"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useRef, useState } from "react"; import { useAccount, useConnect, useDisconnect } from "wagmi"; import { usePortalAuth } from "@/components/auth/portal-auth-provider"; import { Button } from "@/components/ui/button"; import { ErrorBanner } from "@/components/ui/error-banner"; import { Input } from "@/components/ui/input"; import { PasswordInput } from "@/components/ui/password-input"; import { MetaMaskLogo, MaviWalletLogo, } from "@/components/wallet/wallet-logos"; import { getBrivenAuth } from "@/lib/auth"; import { createDemoWalletSession } from "@/lib/demo-session"; import { type LoginMethodId, readLastLoginMethod, writeLastLoginMethod, } from "@/lib/last-login"; import { hasVault, unlockVault } from "@/lib/mavi/storage"; import { establishPortalSession } from "@/lib/portal-session"; const OTP_LEN = 6; function LastUsedBadge({ show }: { show: boolean }) { if (!show) return null; return ( Last used to sign in ); } export function SignInMethods({ redirectTo = "/portal/dashboard", }: { redirectTo?: string; }) { const router = useRouter(); const auth = getBrivenAuth(); const { setDemoSession, clearDemo } = usePortalAuth(); const { address, isConnected } = useAccount(); const { connect, isPending, error: walletError } = useConnect(); const { disconnect } = useDisconnect(); const [email, setEmail] = useState(""); const [otp, setOtp] = useState(""); const [otpOpen, setOtpOpen] = useState(false); const [otpSent, setOtpSent] = useState(false); const [busy, setBusy] = useState(false); const [message, setMessage] = useState(null); const [error, setError] = useState(null); const [maviOpen, setMaviOpen] = useState(false); const [maviPassword, setMaviPassword] = useState(""); const [maviHasVault, setMaviHasVault] = useState(false); const [lastUsed, setLastUsed] = useState(null); const verifyingRef = useRef(false); const lastTriedRef = useRef(""); useEffect(() => { setLastUsed(readLastLoginMethod()); // Open email panel if that was last used if (readLastLoginMethod() === "email") setOtpOpen(true); if (readLastLoginMethod() === "mavi") setMaviOpen(true); setMaviHasVault(hasVault()); }, []); const remember = useCallback((method: LoginMethodId) => { writeLastLoginMethod(method); setLastUsed(method); }, []); const goIn = useCallback( async (opts: { userId: string; method: "email" | "wallet" | "konnos"; loginMethodId: LoginMethodId; email?: string; walletAddress?: string; }) => { remember(opts.loginMethodId); if (opts.method === "wallet" && opts.walletAddress) { setDemoSession(createDemoWalletSession(opts.walletAddress)); } await establishPortalSession({ userId: opts.userId, email: opts.email, method: opts.method, walletAddress: opts.walletAddress, }); window.dispatchEvent(new Event("krypco-auth-change")); router.push(redirectTo); router.refresh(); }, [redirectTo, remember, router, setDemoSession], ); const verifyOtp = useCallback( async (code: string) => { if (!auth) return; const cleaned = code.replace(/\s/g, "").trim(); if (cleaned.length < 4) return; if (verifyingRef.current) return; if (lastTriedRef.current === cleaned) return; verifyingRef.current = true; lastTriedRef.current = cleaned; setError(null); setBusy(true); setMessage("Checking code…"); try { const loginEmail = email.trim().toLowerCase(); const r = await auth.signIn.otpVerify({ email: loginEmail, otp: cleaned, }); if (!r.ok) { setError(r.message || r.code); setMessage(null); lastTriedRef.current = ""; return; } const userId = ("userId" in r && typeof r.userId === "string" && r.userId) || `user_${loginEmail}`; setMessage("Signed in — opening dashboard…"); await goIn({ userId, method: "email", loginMethodId: "email", email: loginEmail, }); } catch (e) { setError(e instanceof Error ? e.message : "Could not verify code"); setMessage(null); lastTriedRef.current = ""; } finally { setBusy(false); verifyingRef.current = false; } }, [auth, email, goIn], ); async function sendOtp() { if (!auth) { setError("Briven Auth is not configured."); return; } setError(null); setMessage(null); setBusy(true); try { const r = await auth.signIn.otpRequest({ email: email.trim(), redirectTo, }); if (!r.ok) { setError(r.message || r.code); return; } setOtpSent(true); setOtp(""); lastTriedRef.current = ""; setMessage("Check your email for a one-time code."); } catch (e) { setError(e instanceof Error ? e.message : "Could not send code"); } finally { setBusy(false); } } function onOtpChange(raw: string) { const digits = raw.replace(/\D/g, "").slice(0, OTP_LEN); setOtp(digits); setError(null); if (digits.length === OTP_LEN) void verifyOtp(digits); } function startKonnos() { if (!auth) { setError("Briven Auth is not configured for Konnos."); return; } setError(null); remember("konnos"); const { redirectUrl } = auth.signIn.social({ provider: "konnos", redirectTo: typeof window !== "undefined" ? `${window.location.origin}${redirectTo}` : redirectTo, }); window.location.assign(redirectUrl); } function startMetaMask() { setError(null); setMaviOpen(false); setMessage(null); connect( { connector: injected() }, { onSuccess: (data) => { const addr = data.accounts[0]; if (!addr) { setError("No account returned from MetaMask."); return; } setMessage("Wallet connected — opening dashboard…"); void goIn({ userId: `wallet_${addr.toLowerCase()}`, method: "wallet", loginMethodId: "metamask", walletAddress: addr, }); }, onError: (e) => { const msg = e.message || "MetaMask connection failed."; if (/provider|injected|ethereum|meta\s*mask/i.test(msg)) { setError( "MetaMask was not found. Install the MetaMask browser extension, then try again.", ); } else if (/reject|denied|user/i.test(msg)) { setError("Connection was cancelled in MetaMask. You can try again."); } else { setError(msg); } }, }, ); } function startMavi() { setError(null); setMessage(null); setMaviHasVault(hasVault()); setMaviOpen((o) => !o); } async function unlockMaviAndEnter() { setError(null); if (!maviPassword) { setError("Enter your mavi password."); return; } setBusy(true); setMessage("Unlocking mavi…"); try { const u = await unlockVault(maviPassword); setMessage("mavi unlocked — opening dashboard…"); await goIn({ userId: `mavi_${u.address.toLowerCase()}`, method: "wallet", loginMethodId: "mavi", walletAddress: u.address, }); } catch (e) { setMessage(null); setError(e instanceof Error ? e.message : "mavi unlock failed"); } finally { setBusy(false); } } return (

Pick any method below. All of them take you into the same dashboard.

{/* 1 — Email OTP */}
{otpOpen ? (
setEmail(e.target.value)} disabled={busy} />
{otpSent ? (
onOtpChange(e.target.value)} disabled={busy} className="mx-auto h-14 w-full max-w-[16rem] text-center font-mono text-2xl tracking-[0.35em]" />

All {OTP_LEN} digits → auto sign-in

) : null}
) : null}
or continue with
{/* 2 — Konnos */}
{/* 3 — MetaMask */}
{/* 4 — mavi wallet */}
{maviOpen ? (
{maviHasVault ? ( <>

Unlock the mavi vault saved in this browser with your password.

void unlockMaviAndEnter()} /> ) : (

No mavi wallet on this browser yet.

Create mavi wallet Import recovery phrase
)}
) : null}
{isConnected && address ? (

{address}

) : null} {message ? (

{message}

) : null} {error || walletError ? ( {error || (walletError?.message?.includes("provider") ? "MetaMask was not found. Install the extension, then try again." : walletError?.message)} ) : null}
); }