Datavision / frontend /src /pages /AuthCallback.tsx
DataVision CI/CD Bot
release: clean production build for HuggingFace Space
09801ca
Raw
History Blame Contribute Delete
6.13 kB
/**
* Auth Callback - PRODUCTION READY
* Handles OAuth redirects and email confirmations
*/
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { auth } from '../lib/auth-client';
export default function AuthCallback() {
const navigate = useNavigate();
const [error, setError] = useState<string | null>(null);
const [isEmailConfirm, setIsEmailConfirm] = useState(false);
useEffect(() => {
let isMounted = true;
let attempts = 0;
// Check for error in URL
const params = new URLSearchParams(window.location.search);
const hashParams = new URLSearchParams(window.location.hash.substring(1));
const urlError = params.get('error') || hashParams.get('error');
// Check if this is an email confirmation callback
const type = hashParams.get('type') || params.get('type');
if (type === 'signup' || type === 'email_confirmation' || type === 'recovery') {
setIsEmailConfirm(true);
}
if (urlError) {
const desc = params.get('error_description') || hashParams.get('error_description');
setError(desc || urlError);
return;
}
// Native OAuth token from FastAPI
const customToken = params.get('token');
if (customToken) {
localStorage.setItem('auth_token', customToken);
fetch(`${import.meta.env.VITE_API_URL || ''}/api/v1/auth/me`, {
headers: { 'Authorization': `Bearer ${customToken}` }
})
.then(res => res.json())
.then(data => {
if (data.success && isMounted) {
localStorage.setItem('auth_user', JSON.stringify(data.user));
localStorage.setItem('userId', data.user.id);
window.location.href = '/data-hub';
} else if (isMounted) {
setError('Failed to fetch user profile');
}
})
.catch(() => {
if (isMounted) setError('Authentication error');
});
return;
}
// Poll for session (Legacy fallback)
const checkSession = async () => {
attempts++;
const { data: { session } } = await auth.getSession();
if (!isMounted) return;
if (session) {
// Store userId for API calls
localStorage.setItem('userId', session.user.id);
// If email confirmation, show success briefly then redirect to data-hub
if (isEmailConfirm) {
setTimeout(() => window.location.href = '/data-hub', 1500);
} else {
// OAuth login - go directly to data-hub
window.location.href = '/data-hub';
}
} else if (attempts < 20) {
// Keep polling (10 seconds max)
setTimeout(checkSession, 500);
} else {
// No session - might be just email confirmation without auto-login
if (isEmailConfirm) {
setTimeout(() => navigate('/login', { replace: true }), 2000);
} else {
setError('Authentication timeout. Please try again.');
}
}
};
// Wait a bit for URL tokens to be processed, then start checking
setTimeout(checkSession, 300);
return () => { isMounted = false; };
}, [navigate, isEmailConfirm]);
if (error) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-900 p-4">
<div className="bg-gray-800 rounded-2xl p-8 max-w-md w-full text-center">
<div className="w-16 h-16 bg-red-500/20 rounded-full flex items-center justify-center mx-auto mb-4">
<svg className="w-8 h-8 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
<h2 className="text-xl font-bold text-white mb-2">Login Failed</h2>
<p className="text-gray-400 mb-6">{error}</p>
<a href="/login" className="px-6 py-2 bg-green-500 text-white rounded-lg inline-block">
Try Again
</a>
</div>
</div>
);
}
// Email confirmation success view
if (isEmailConfirm) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-900 p-4">
<div className="bg-gray-800 rounded-2xl p-8 max-w-md w-full text-center">
<div className="w-16 h-16 bg-green-500/20 rounded-full flex items-center justify-center mx-auto mb-4">
<svg className="w-8 h-8 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
</div>
<h2 className="text-xl font-bold text-white mb-2">Email Confirmed! 🎉</h2>
<p className="text-gray-400 mb-4">Your account is now verified.</p>
<div className="w-8 h-8 border-4 border-green-500 border-t-transparent rounded-full animate-spin mx-auto"></div>
<p className="text-gray-500 text-sm mt-2">Redirecting...</p>
</div>
</div>
);
}
return (
<div className="min-h-screen flex items-center justify-center bg-gray-900">
<div className="text-center">
<div className="w-12 h-12 border-4 border-green-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
<p className="text-white">Completing login...</p>
</div>
</div>
);
}