import { useConnectModal } from "@rainbow-me/rainbowkit"; import { QRCodeSVG } from "qrcode.react"; import { useEffect, useRef, useState } from "react"; import { useAccount, useSignMessage } from "wagmi"; import { useToast } from "../components/Toast"; import { useI18n } from "../i18n"; import { createTokenPocketLoginRequest, fetchTokenPocketLoginResult, requestWalletNonce, verifyWalletSignature, type TokenPocketLoginRequest, } from "./api"; import { openWalletDeepLink } from "./deepLinks"; import { useWallet } from "./WalletProvider"; const pollIntervalMs = 1800; type ModalState = "idle" | "tpLoading" | "tpPolling" | "rainbowSigning"; export function WalletLoginModal() { const { t } = useI18n(); const { showToast } = useToast(); const { closeLoginModal, completeLogin, loginModalOpen, signInInjected } = useWallet(); const { openConnectModal } = useConnectModal(); const { address, isConnected } = useAccount(); const { signMessageAsync } = useSignMessage(); const [state, setState] = useState("idle"); const [error, setError] = useState(""); const [tpRequest, setTpRequest] = useState( null, ); const [rainbowPending, setRainbowPending] = useState(false); const rainbowSigningRef = useRef(false); const close = () => { if (state === "tpLoading" || state === "rainbowSigning") return; closeLoginModal(); setError(""); }; useEffect(() => { if (!loginModalOpen || !tpRequest) return; if (state !== "tpPolling") return; let cancelled = false; const poll = async () => { try { const result = await fetchTokenPocketLoginResult(tpRequest.actionId); if (cancelled) return; if (result.status === "completed") { const verified = await verifyWalletSignature({ address: result.address, message: result.message, signature: result.signature, }); if (cancelled) return; completeLogin(verified.token, verified.wallet); showToast(t("walletLoginSuccess")); setState("idle"); setTpRequest(null); return; } if (result.status === "expired" || result.status === "failed") { setState("idle"); setError(result.error || t("walletTpExpired")); } } catch { if (!cancelled) setError(t("walletLoginFailed")); } }; void poll(); const timer = window.setInterval(() => void poll(), pollIntervalMs); return () => { cancelled = true; window.clearInterval(timer); }; }, [completeLogin, loginModalOpen, state, tpRequest, t, showToast]); useEffect(() => { if (!rainbowPending || !isConnected || !address) return; if (rainbowSigningRef.current) return; rainbowSigningRef.current = true; setState("rainbowSigning"); setError(""); void (async () => { try { const nonce = await requestWalletNonce(address); const signature = await signMessageAsync({ message: nonce.message }); const verified = await verifyWalletSignature({ address, message: nonce.message, signature, }); completeLogin(verified.token, verified.wallet); showToast(t("walletLoginSuccess")); } catch (err) { const message = err instanceof Error ? err.message : t("walletLoginFailed"); setError(message || t("walletLoginFailed")); showToast(t("walletLoginFailed"), "error"); } finally { setState("idle"); setRainbowPending(false); rainbowSigningRef.current = false; } })(); }, [ address, isConnected, rainbowPending, showToast, signMessageAsync, t, completeLogin, ]); if (!loginModalOpen) return null; const startInjected = async () => { setError(""); setState("idle"); await signInInjected().catch(() => undefined); }; const startTokenPocketQr = async () => { setError(""); setState("tpLoading"); try { const req = await createTokenPocketLoginRequest(); setTpRequest(req); setState("tpPolling"); } catch { setState("idle"); setError(t("walletTpQrFailed")); } }; const startRainbowFallback = () => { setError(""); setRainbowPending(true); openConnectModal?.(); if (!openConnectModal) setError(t("walletRainbowUnavailable")); }; return (

{t("walletLoginTitle")}

{t("walletLoginDesc")}

{t("walletTokenPocketQr")}

{t("walletTokenPocketQrDesc")}

{tpRequest ? (

{t("walletQrUseAnotherDevice")}

) : null}

{t("walletRainbowFallback")}

{t("walletRainbowFallbackDesc")}

{t("walletNetworkWarning")}

{error ? (

{error}

) : null}
); }