// Copyright (c) 2026 PowerOn AG // All rights reserved. import { useNavigate, useLocation } from 'react-router-dom'; import { useState, useEffect, useRef, useCallback } from 'react'; import { FaGoogle, FaMicrosoft, FaEnvelopeOpenText, FaShieldAlt } from 'react-icons/fa'; import { useAuth, useMsalAuth, useGoogleAuth } from '../hooks/useAuthentication'; import { generateAndStoreCSRFToken } from '../utils/csrfUtils'; import { PENDING_INVITATION_KEY } from './InvitePage'; import OnboardingWizard from '../components/OnboardingWizard'; import { mfaConfirmApi } from '../api/authApi'; import styles from './Login.module.css'; import { LanguageSelector } from '../components/UiComponents/LanguageSelector'; import { useLanguage } from '../providers/language/LanguageContext'; import { useDocumentTitle } from '../hooks/useDocumentTitle'; type LoginPhase = 'credentials' | 'mfa_code' | 'mfa_setup'; function Login() { const { t } = useLanguage(); const navigate = useNavigate(); const location = useLocation(); const invitationUsername = (location.state as any)?.invitationUsername || ''; const [username, setUsername] = useState(invitationUsername); const [password, setPassword] = useState(''); const [usernameFocused, setUsernameFocused] = useState(false); const [passwordFocused, setPasswordFocused] = useState(false); const { login, verifyMfa, error: loginError, isLoading: isLoginLoading } = useAuth(); const { loginWithMsal, error: msalError, isLoading: isMsalLoading } = useMsalAuth(); const { loginWithGoogle, error: googleError, isLoading: isGoogleLoading } = useGoogleAuth(); const [showOnboardingWizard, setShowOnboardingWizard] = useState(false); const [phase, setPhase] = useState('credentials'); const [mfaToken, setMfaToken] = useState(null); const [mfaCode, setMfaCode] = useState(''); const [mfaError, setMfaError] = useState(null); const [mfaLoading, setMfaLoading] = useState(false); const [provisioningUri, setProvisioningUri] = useState(null); const [mfaSetupStep, setMfaSetupStep] = useState<'qr' | 'confirm'>('qr'); const [mfaConfirmCode, setMfaConfirmCode] = useState(''); const mfaInputRef = useRef(null); const pendingInvitationToken = localStorage.getItem(PENDING_INVITATION_KEY); const hasPendingInvitation = !!pendingInvitationToken; const fromLocation = location.state?.from; const from = (fromLocation?.pathname || "/") + (fromLocation?.search || ""); useDocumentTitle(t('Login')); useEffect(() => { generateAndStoreCSRFToken(); }, []); useEffect(() => { const checkAutofill = () => { const usernameInput = document.querySelector('input[type="text"]') as HTMLInputElement; const passwordInput = document.querySelector('input[type="password"]') as HTMLInputElement; if (usernameInput && usernameInput.value) setUsername(usernameInput.value); if (passwordInput && passwordInput.value) setPassword(passwordInput.value); }; checkAutofill(); const timer = setTimeout(checkAutofill, 100); return () => clearTimeout(timer); }, []); useEffect(() => { if (phase === 'mfa_code' && mfaInputRef.current) { mfaInputRef.current.focus(); } }, [phase]); const handleSuccessfulLogin = useCallback(() => { if (pendingInvitationToken) { navigate(`/invite/${pendingInvitationToken}`, { replace: true }); } else { navigate(from, { replace: true }); } }, [pendingInvitationToken, navigate, from]); const _handleMfaResponse = useCallback(async (response: any) => { if (response.type === 'mfa_required') { setMfaToken(response.mfaToken); setPhase('mfa_code'); } else if (response.type === 'mfa_setup_required') { setMfaToken(response.mfaToken); setProvisioningUri(response.provisioningUri || null); setPhase('mfa_setup'); setMfaSetupStep('qr'); } }, [t]); const handleMsalLogin = async () => { try { const response = await loginWithMsal(); if (response?.type === 'mfa_required' || response?.type === 'mfa_setup_required') { await _handleMfaResponse(response); return; } handleSuccessfulLogin(); } catch (error) { console.error("MSAL login failed:", error); } }; const handleGoogleLogin = async () => { try { const response = await loginWithGoogle(); if (response?.type === 'mfa_required' || response?.type === 'mfa_setup_required') { await _handleMfaResponse(response); return; } if ((response as any)?.isNewUser) { setShowOnboardingWizard(true); return; } handleSuccessfulLogin(); } catch (error) { console.error("Google login failed:", error); } }; const handleCredentialLogin = async (e?: React.MouseEvent) => { e?.preventDefault(); try { const response = await login(username, password); if (response.type === 'mfa_required' || response.type === 'mfa_setup_required') { await _handleMfaResponse(response); return; } handleSuccessfulLogin(); } catch (error) { console.error("Login failed:", error); } }; const _handleMfaVerify = async (code?: string) => { const c = code ?? mfaCode; if (!mfaToken || c.length < 6) return; setMfaError(null); setMfaLoading(true); try { await verifyMfa(mfaToken, c); handleSuccessfulLogin(); } catch { setMfaError(t('Ungültiger MFA-Code')); setMfaCode(''); } finally { setMfaLoading(false); } }; const _handleMfaSetupConfirm = async (code?: string) => { const c = code ?? mfaConfirmCode; if (c.length < 6) return; setMfaError(null); setMfaLoading(true); try { await mfaConfirmApi(c, mfaToken || undefined); setPhase('mfa_code'); setMfaCode(''); setMfaError(null); } catch { setMfaError(t('Ungültiger Code – bitte erneut versuchen')); setMfaConfirmCode(''); } finally { setMfaLoading(false); } }; const _handleBackToCredentials = () => { setPhase('credentials'); setMfaToken(null); setMfaCode(''); setMfaError(null); setProvisioningUri(null); setMfaConfirmCode(''); }; if (showOnboardingWizard) { return ( { setShowOnboardingWizard(false); handleSuccessfulLogin(); }} onDismiss={() => { setShowOnboardingWizard(false); handleSuccessfulLogin(); }} /> ); } const _renderMfaCodePhase = () => ( <>

{t('Zwei-Faktor-Authentifizierung')}

{t('Geben Sie den 6-stelligen Code aus Ihrer Authenticator-App ein.')}

{mfaError &&
{mfaError}
}
{ const val = e.target.value.replace(/\D/g, '').slice(0, 6); setMfaCode(val); if (val.length === 6) _handleMfaVerify(val); }} className={`${styles.input} ${mfaCode ? styles.focused : ''}`} style={{ textAlign: 'center', letterSpacing: '0.5em', fontSize: '1.4rem' }} />
); const _renderMfaSetupPhase = () => ( <>

{t('MFA-Setup erforderlich')}

{t('Scannen Sie den QR-Code mit Ihrer Authenticator-App (z.B. Microsoft Authenticator, Google Authenticator).')}

{mfaError &&
{mfaError}
} {mfaLoading && !provisioningUri && (
{t('wird geladen…')}
)} {mfaSetupStep === 'qr' && provisioningUri && ( <>
MFA QR Code
)} {mfaSetupStep === 'confirm' && ( <>

{t('Geben Sie den 6-stelligen Code aus Ihrer Authenticator-App ein, um das Setup abzuschliessen.')}

{ const val = e.target.value.replace(/\D/g, '').slice(0, 6); setMfaConfirmCode(val); if (val.length === 6) _handleMfaSetupConfirm(val); }} className={`${styles.input} ${mfaConfirmCode ? styles.focused : ''}`} style={{ textAlign: 'center', letterSpacing: '0.5em', fontSize: '1.4rem' }} />
)}
); return (
PowerOn
{phase === 'mfa_code' && _renderMfaCodePhase()} {phase === 'mfa_setup' && _renderMfaSetupPhase()} {phase === 'credentials' && ( <> {hasPendingInvitation && (
{t('Sie haben eine ausstehende Einladung')}
)} {(loginError || msalError || googleError) && (
{loginError || msalError || googleError}
)}
setUsername(e.target.value)} onFocus={() => setUsernameFocused(true)} onBlur={() => setUsernameFocused(false)} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); handleCredentialLogin(); } }} className={`${styles.input} ${usernameFocused || username ? styles.focused : ''}`} />
setPassword(e.target.value)} onFocus={() => setPasswordFocused(true)} onBlur={() => setPasswordFocused(false)} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); handleCredentialLogin(); } }} className={`${styles.input} ${passwordFocused || password ? styles.focused : ''}`} />

{t('Mit der Anmeldung stimmen Sie unseren Datenschutzbestimmungen zur KI-Nutzung zu.')}

{t('oder')}
{t('Du hast noch kein Konto?')}
)}
); } export default Login;