'use client'; import '@farcaster/auth-kit/styles.css'; import { useSignIn } from '@farcaster/auth-kit'; import { useCallback, useEffect, useState, useRef } from 'react'; import { cn } from '../../lib/utils'; import { Button } from './Button'; // Utility functions for device detection function isAndroid(): boolean { return ( typeof navigator !== 'undefined' && /android/i.test(navigator.userAgent) ); } function isSmallIOS(): boolean { return ( typeof navigator !== 'undefined' && /iPhone|iPod/.test(navigator.userAgent) ); } function isLargeIOS(): boolean { return ( typeof navigator !== 'undefined' && (/iPad/.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) ); } function isIOS(): boolean { return isSmallIOS() || isLargeIOS(); } function isMobile(): boolean { return isAndroid() || isIOS(); } // Hook for detecting clicks outside an element function useDetectClickOutside( ref: React.RefObject, callback: () => void ) { useEffect(() => { function handleClickOutside(event: MouseEvent) { if (ref.current && !ref.current.contains(event.target as Node)) { callback(); } } document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [ref, callback]); } // Storage utilities for persistence const STORAGE_KEY = 'farcaster_auth_state'; interface StoredAuthState { isAuthenticated: boolean; userData?: { fid?: number; pfpUrl?: string; username?: string; }; lastSignInTime?: number; } function saveAuthState(state: StoredAuthState) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } catch (error) { console.warn('Failed to save auth state:', error); } } function loadAuthState(): StoredAuthState | null { try { const stored = localStorage.getItem(STORAGE_KEY); return stored ? JSON.parse(stored) : null; } catch (error) { console.warn('Failed to load auth state:', error); return null; } } function clearAuthState() { try { localStorage.removeItem(STORAGE_KEY); } catch (error) { console.warn('Failed to clear auth state:', error); } } // QR Code Dialog Component function QRCodeDialog({ open, onClose, url, isError, error, }: { open: boolean; onClose: () => void; url: string; isError: boolean; error?: Error | null; }) { if (!open) return null; return (

{isError ? 'Error' : 'Sign in with Farcaster'}

{isError ? (
{error?.message || 'Unknown error, please try again.'}
) : (

To sign in with Farcaster, scan the code below with your phone's camera.

{/* eslint-disable-next-line @next/next/no-img-element */} QR Code for Farcaster sign in
)}
); } // Profile Button Component function ProfileButton({ userData, onSignOut, }: { userData?: { fid?: number; pfpUrl?: string; username?: string }; onSignOut: () => void; }) { const [showDropdown, setShowDropdown] = useState(false); const ref = useRef(null); useDetectClickOutside(ref, () => setShowDropdown(false)); const name = userData?.username ?? `!${userData?.fid}`; const pfpUrl = userData?.pfpUrl ?? 'https://farcaster.xyz/avatar.png'; return (
{showDropdown && (
)}
); } // Main Custom SignInButton Component export function NeynarAuthButton() { const [nonce, setNonce] = useState(null); const [showDialog, setShowDialog] = useState(false); const [storedAuth, setStoredAuth] = useState(null); // Generate nonce useEffect(() => { const generateNonce = async () => { try { const response = await fetch('/api/auth/nonce'); if (response.ok) { const data = await response.json(); setNonce(data.nonce); } else { console.error('Failed to fetch nonce'); } } catch (error) { console.error('Error generating nonce:', error); } }; generateNonce(); }, []); // Load stored auth state on mount useEffect(() => { const stored = loadAuthState(); if (stored && stored.isAuthenticated) { setStoredAuth(stored); } }, []); // Success callback - this is critical! const onSuccessCallback = useCallback((res: unknown) => { console.log('🎉 Sign in successful!', res); const authState: StoredAuthState = { isAuthenticated: true, userData: res as StoredAuthState['userData'], lastSignInTime: Date.now(), }; saveAuthState(authState); setStoredAuth(authState); setShowDialog(false); }, []); // Status response callback const onStatusCallback = useCallback((statusData: unknown) => { console.log('📊 Status response:', statusData); }, []); // Error callback const onErrorCallback = useCallback((error?: Error | null) => { console.error('❌ Sign in error:', error); }, []); const signInState = useSignIn({ nonce: nonce || undefined, onSuccess: onSuccessCallback, onStatusResponse: onStatusCallback, onError: onErrorCallback, }); const { signIn, signOut, connect, reconnect, isSuccess, isError, error, channelToken, url, data, validSignature, isPolling, } = signInState; // Connect when component mounts and we have a nonce useEffect(() => { if (nonce && !channelToken) { console.log('🔌 Connecting with nonce:', nonce); connect(); } }, [nonce, channelToken, connect]); // Debug logging useEffect(() => { console.log('🔍 Auth state:', { isSuccess, validSignature, hasData: !!data, isPolling, isError, storedAuth: !!storedAuth?.isAuthenticated, }); }, [isSuccess, validSignature, data, isPolling, isError, storedAuth]); // Handle fetching signers after successful authentication useEffect(() => { if (data?.message && data?.signature) { console.log('📝 Got message and signature:', { message: data.message, signature: data.signature, }); const fetchSigners = async () => { try { const response = await fetch( `/api/auth/signer?message=${encodeURIComponent( data.message || '' )}&signature=${data.signature}` ); const signerData = await response.json(); console.log('🔐 Signer response:', signerData); if (response.ok) { console.log('✅ Signers fetched successfully:', signerData.signers); } else { console.error('❌ Failed to fetch signers'); } } catch (error) { console.error('❌ Error fetching signers:', error); } }; fetchSigners(); } }, [data?.message, data?.signature]); const handleSignIn = useCallback(() => { console.log('🚀 Starting sign in flow...'); if (isError) { console.log('🔄 Reconnecting due to error...'); reconnect(); } setShowDialog(true); signIn(); // Open mobile app if on mobile and URL is available if (url && isMobile()) { console.log('📱 Opening mobile app:', url); window.open(url, '_blank'); } }, [isError, reconnect, signIn, url]); const handleSignOut = useCallback(() => { console.log('👋 Signing out...'); setShowDialog(false); signOut(); clearAuthState(); setStoredAuth(null); }, [signOut]); // The key fix: match the original library's authentication logic exactly const authenticated = (isSuccess && validSignature) || storedAuth?.isAuthenticated; const userData = data || storedAuth?.userData; // Show loading state while nonce is being fetched if (!nonce) { return (
Loading...
); } return ( <> {authenticated ? ( ) : ( )} {/* QR Code Dialog for desktop */} {url && ( setShowDialog(false)} url={url} isError={isError} error={error} /> )} {/* Debug panel (optional - can be removed in production) */} {/* {process.env.NODE_ENV === "development" && (
Debug Info:
            {JSON.stringify(
              {
                authenticated,
                isSuccess,
                validSignature,
                hasData: !!data,
                isPolling,
                isError,
                hasStoredAuth: !!storedAuth?.isAuthenticated,
                hasUrl: !!url,
                hasChannelToken: !!channelToken,
              },
              null,
              2
            )}
          
)} */} ); }