import { useEffect, useState } from 'react'; import { invokeWithTracking } from '@/lib/edgeFunctionTracking'; import { useNavigate } from 'react-router-dom'; import { supabase } from '@/lib/supabaseClient'; import { useToast } from '@/hooks/use-toast'; import { Loader2, CheckCircle2 } from 'lucide-react'; import { Header } from '@/components/layout/Header'; import { handlePostAuthFlow, verifyMfaUpgrade } from '@/lib/authService'; import type { AuthMethod } from '@/types/auth'; import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card'; import { Label } from '@/components/ui/label'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { getErrorMessage } from '@/lib/errorHandler'; import { MFAStepUpModal } from '@/components/auth/MFAStepUpModal'; import { useDocumentTitle } from '@/hooks/useDocumentTitle'; export default function AuthCallback() { useDocumentTitle('Sign In - Processing'); const navigate = useNavigate(); const { toast } = useToast(); const [status, setStatus] = useState<'processing' | 'success' | 'error' | 'mfa_required'>('processing'); const [mfaFactorId, setMfaFactorId] = useState(null); const [isRecoveryMode, setIsRecoveryMode] = useState(false); const [newPassword, setNewPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [settingPassword, setSettingPassword] = useState(false); useEffect(() => { const processOAuthCallback = async () => { try { // Check if this is a password recovery flow first const hash = window.location.hash; if (hash.includes('type=recovery')) { setIsRecoveryMode(true); setStatus('success'); // Stop the loading spinner return; // Don't process further } // Get the current session const { data: { session }, error: sessionError } = await supabase.auth.getSession(); if (sessionError) { throw sessionError; } if (!session) { navigate('/auth'); return; } const user = session.user; // CRITICAL: Check ban status immediately after getting session const { data: banProfile } = await supabase .from('profiles') .select('banned, ban_reason') .eq('user_id', user.id) .single(); if (banProfile?.banned) { await supabase.auth.signOut(); const reason = banProfile.ban_reason ? `Reason: ${banProfile.ban_reason}` : 'Contact support for assistance.'; toast({ variant: 'destructive', title: 'Account Suspended', description: `Your account has been suspended. ${reason}`, duration: 10000 }); navigate('/auth'); return; // Stop OAuth processing } // Check if this is a new OAuth user (created within last minute) const createdAt = new Date(user.created_at); const now = new Date(); const isNewUser = (now.getTime() - createdAt.getTime()) < 60000; // 1 minute // Check if user has an OAuth provider const provider = user.app_metadata?.provider; const isOAuthUser = provider === 'google' || provider === 'discord'; // If new OAuth user, process profile if (isNewUser && isOAuthUser) { setStatus('processing'); try { const { data, error, requestId } = await invokeWithTracking( 'process-oauth-profile', {}, user.id ); if (error) { // Don't throw - allow sign-in to continue even if profile processing fails } } catch (error) { // Continue anyway - don't block sign-in } } // Determine authentication method let authMethod: AuthMethod = 'magiclink'; if (isOAuthUser) { authMethod = 'oauth'; } // Unified post-authentication flow for ALL methods (OAuth, magic link, etc.) const result = await handlePostAuthFlow(session, authMethod); if (result.success && result.data?.shouldRedirect) { // Get factor ID and show modal instead of redirecting const { data: factors } = await supabase.auth.mfa.listFactors(); const totpFactor = factors?.totp?.find(f => f.status === 'verified'); if (totpFactor) { setMfaFactorId(totpFactor.id); setStatus('mfa_required'); return; } } setStatus('success'); // Show success message toast({ title: 'Welcome to ThrillWiki!', description: isNewUser ? 'Your account has been created successfully.' : 'You have been signed in successfully.', }); // Redirect to home after a short delay setTimeout(() => { navigate('/'); }, 500); } catch (error) { const errorMsg = getErrorMessage(error); setStatus('error'); toast({ variant: 'destructive', title: 'Sign in error', description: errorMsg, }); // Redirect to auth page after error setTimeout(() => { navigate('/auth'); }, 2000); } }; processOAuthCallback(); }, [navigate, toast]); const handleMfaSuccess = async () => { const { data: { session } } = await supabase.auth.getSession(); const verification = await verifyMfaUpgrade(session); if (!verification.success) { toast({ variant: "destructive", title: "MFA Verification Failed", description: verification.error || "Failed to upgrade session." }); await supabase.auth.signOut(); navigate('/auth'); return; } setMfaFactorId(null); setStatus('success'); toast({ title: 'Welcome to ThrillWiki!', description: 'You have been signed in successfully.', }); setTimeout(() => navigate('/'), 500); }; const handlePasswordReset = async (e: React.FormEvent) => { e.preventDefault(); if (newPassword !== confirmPassword) { toast({ variant: 'destructive', title: 'Passwords do not match', description: 'Please make sure both passwords are identical.', }); return; } if (newPassword.length < 8) { toast({ variant: 'destructive', title: 'Password too short', description: 'Password must be at least 8 characters long.', }); return; } setSettingPassword(true); try { const { error } = await supabase.auth.updateUser({ password: newPassword }); if (error) throw error; toast({ title: 'Password Set Successfully!', description: 'You can now sign in with your email and password.', }); setTimeout(() => { navigate('/auth'); }, 1500); } catch (error) { const errorMsg = getErrorMessage(error); toast({ variant: 'destructive', title: 'Failed to set password', description: errorMsg, }); setSettingPassword(false); } }; return (
{isRecoveryMode ? ( Set Your New Password Enter a new password for your account
setNewPassword(e.target.value)} placeholder="At least 8 characters" required minLength={8} />
setConfirmPassword(e.target.value)} placeholder="Re-enter password" required />
) : (
{status === 'processing' && ( <>

Setting up your profile...

We're preparing your ThrillWiki experience

)} {status === 'success' && ( <>

Welcome!

Redirecting you to ThrillWiki...

)} {status === 'error' && ( <>

Something went wrong

Redirecting you to sign in...

)}
)}
{status === 'mfa_required' && mfaFactorId && ( { setMfaFactorId(null); navigate('/auth'); }} /> )}
); }