import React, { useState, useMemo, useCallback } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useAuth } from '../contexts/AuthContext'; import { getToken } from '../services/authService'; import ThemeSwitch from '@/components/ui/ThemeSwitch'; import LanguageSwitch from '@/components/ui/LanguageSwitch'; import DefaultPasswordWarningModal from '@/components/ui/DefaultPasswordWarningModal'; const sanitizeReturnUrl = (value: string | null): string | null => { if (!value) { return null; } try { // Support both relative paths and absolute URLs on the same origin const origin = typeof window !== 'undefined' ? window.location.origin : 'http://localhost'; const url = new URL(value, origin); if (url.origin !== origin) { return null; } const relativePath = `${url.pathname}${url.search}${url.hash}`; return relativePath || '/'; } catch { if (value.startsWith('/') && !value.startsWith('//')) { return value; } return null; } }; const LoginPage: React.FC = () => { const { t } = useTranslation(); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const [showDefaultPasswordWarning, setShowDefaultPasswordWarning] = useState(false); const { login } = useAuth(); const location = useLocation(); const navigate = useNavigate(); const returnUrl = useMemo(() => { const params = new URLSearchParams(location.search); return sanitizeReturnUrl(params.get('returnUrl')); }, [location.search]); const isServerUnavailableError = useCallback((message?: string) => { if (!message) return false; const normalized = message.toLowerCase(); return ( normalized.includes('failed to fetch') || normalized.includes('networkerror') || normalized.includes('network error') || normalized.includes('connection refused') || normalized.includes('unable to connect') || normalized.includes('fetch error') || normalized.includes('econnrefused') || normalized.includes('http 500') || normalized.includes('internal server error') || normalized.includes('proxy error') ); }, []); const buildRedirectTarget = useCallback(() => { if (!returnUrl) { return '/'; } // Only attach JWT when returning to the OAuth authorize endpoint if (!returnUrl.startsWith('/oauth/authorize')) { return returnUrl; } const token = getToken(); if (!token) { return returnUrl; } try { const origin = window.location.origin; const url = new URL(returnUrl, origin); url.searchParams.set('token', token); return `${url.pathname}${url.search}${url.hash}`; } catch { const separator = returnUrl.includes('?') ? '&' : '?'; return `${returnUrl}${separator}token=${encodeURIComponent(token)}`; } }, [returnUrl]); const redirectAfterLogin = useCallback(() => { if (returnUrl) { window.location.assign(buildRedirectTarget()); } else { navigate('/'); } }, [buildRedirectTarget, navigate, returnUrl]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(null); setLoading(true); try { if (!username || !password) { setError(t('auth.emptyFields')); setLoading(false); return; } const result = await login(username, password); if (result.success) { if (result.isUsingDefaultPassword) { // Show warning modal instead of navigating immediately setShowDefaultPasswordWarning(true); } else { redirectAfterLogin(); } } else { const message = result.message; if (isServerUnavailableError(message)) { setError(t('auth.serverUnavailable')); } else { setError(t('auth.loginFailed')); } } } catch (err) { const message = err instanceof Error ? err.message : undefined; if (isServerUnavailableError(message)) { setError(t('auth.serverUnavailable')); } else { setError(t('auth.loginError')); } } finally { setLoading(false); } }; const handleCloseWarning = () => { setShowDefaultPasswordWarning(false); redirectAfterLogin(); }; return (
{/* Top-right controls */}
{/* Tech background layer */}
{/* Main content */}
{/* Centered slogan */}

{t('auth.slogan')}

{/* Centered login card */}
setUsername(e.target.value)} />
setPassword(e.target.value)} />
{error && (
{error}
)}
{/* Default Password Warning Modal */}
); }; export default LoginPage;