frontend_nyla/src/pages/Login.tsx

256 lines
9.1 KiB
TypeScript

import { useNavigate, useLocation } from 'react-router-dom';
import { useState, useEffect } from 'react';
import { FaGoogle, FaMicrosoft, FaEnvelopeOpenText } 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 styles from './Login.module.css';
function Login() {
const navigate = useNavigate();
const location = useLocation();
// Pre-fill username from invitation if provided via location.state
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, error: loginError, isLoading: isLoginLoading } = useAuth();
const { loginWithMsal, error: msalError, isLoading: isMsalLoading } = useMsalAuth();
const { loginWithGoogle, error: googleError, isLoading: isGoogleLoading } = useGoogleAuth();
const [showOnboardingWizard, setShowOnboardingWizard] = useState(false);
// Check for pending invitation
const pendingInvitationToken = localStorage.getItem(PENDING_INVITATION_KEY);
const hasPendingInvitation = !!pendingInvitationToken;
const fromLocation = location.state?.from;
const from = (fromLocation?.pathname || "/") + (fromLocation?.search || "");
// Set page title and generate CSRF token
useEffect(() => {
document.title = "PowerOn AI Platform - Login";
// Generate CSRF token for new security implementation
generateAndStoreCSRFToken();
}, []);
// Check for autofilled inputs
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);
}
};
// Check immediately and after a short delay
checkAutofill();
const timer = setTimeout(checkAutofill, 100);
return () => clearTimeout(timer);
}, []);
// Handle redirect after successful login
const handleSuccessfulLogin = () => {
// If there's a pending invitation, redirect to accept it
if (pendingInvitationToken) {
navigate(`/invite/${pendingInvitationToken}`, { replace: true });
} else {
navigate(from, { replace: true });
}
};
const handleMsalLogin = async () => {
try {
console.log("Attempting MSAL login...");
const response = await loginWithMsal();
console.log("MSAL login successful:", response);
handleSuccessfulLogin();
} catch (error) {
console.error("MSAL login failed:", error);
}
};
const handleGoogleLogin = async () => {
try {
console.log("Attempting Google login...");
const response = await loginWithGoogle();
console.log("Google login successful:", response);
if (response?.isNewUser) {
setShowOnboardingWizard(true);
return;
}
handleSuccessfulLogin();
} catch (error) {
console.error("Google login failed:", error);
}
};
const handleCredentialLogin = async (e?: React.MouseEvent) => {
e?.preventDefault(); // Prevent default form submission
try {
console.log("Attempting login with:", username);
await login(username, password);
console.log("Login successful");
handleSuccessfulLogin();
} catch (error) {
console.error("Login failed:", error);
// Stay on login page to show error message
// The error will be displayed via the loginError state from useAuth hook
}
};
if (showOnboardingWizard) {
return (
<OnboardingWizard
onComplete={() => {
setShowOnboardingWizard(false);
handleSuccessfulLogin();
}}
onDismiss={() => {
setShowOnboardingWizard(false);
handleSuccessfulLogin();
}}
/>
);
}
return (
<div className={styles.container}>
<div className={styles.mainContent}>
<div className={styles.logo}>
<img
src="/logos/poweron-logo.png"
alt="PowerOn"
className={styles.logoImage}
/>
</div>
<div className={styles.loginSection}>
<div className={styles.loginBox}>
<div className={styles.loginForm}>
{/* Pending invitation notice */}
{hasPendingInvitation && (
<div className={styles.invitationNotice}>
<FaEnvelopeOpenText className={styles.invitationIcon} />
<span>Sie haben eine ausstehende Einladung. Bitte melden Sie sich an, um diese anzunehmen.</span>
</div>
)}
{(loginError || msalError || googleError) && (
<div className={styles.error}>{loginError || msalError || googleError}</div>
)}
<div className={styles.floatingLabelInput}>
<input
type="text"
placeholder=" "
value={username}
onChange={(e) => 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 : ''}`}
/>
<label className={usernameFocused || username ? styles.focusedLabel : styles.label}>Benutzername</label>
</div>
<div className={styles.floatingLabelInput}>
<input
type="password"
placeholder=" "
value={password}
onChange={(e) => 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 : ''}`}
/>
<label className={passwordFocused || password ? styles.focusedLabel : styles.label}>Passwort</label>
</div>
<div className={styles.disclaimer}>
<p>
Mit der Anmeldung stimmen Sie unseren Datenschutzbestimmungen zur KI-Nutzung zu.
</p>
</div>
<button
className={`${styles.button} ${styles.loginButton}`}
onClick={handleCredentialLogin}
disabled={isLoginLoading}
>
{isLoginLoading ? "wird geladen..." : "Anmelden"}
</button>
<div className={styles.passwordResetLink}>
<button
className={styles.textButton}
onClick={() => navigate("/password-reset-request")}
>
Passwort vergessen?
</button>
</div>
<div className={styles.divider}>
<span>oder</span>
</div>
<button
className={`${styles.button} ${styles.microsoftButton}`}
onClick={handleMsalLogin}
disabled={isMsalLoading}
>
<div className={styles.buttonContent}>
<FaMicrosoft />
{isMsalLoading ? "Signing in..." : "Mit Microsoft anmelden"}
</div>
</button>
<button
className={`${styles.button} ${styles.googleButton}`}
onClick={handleGoogleLogin}
disabled={isGoogleLoading}
>
<div className={styles.buttonContent}>
<FaGoogle />
{isGoogleLoading ? "Signing in..." : "Mit Google anmelden"}
</div>
</button>
<div className={styles.registerLink}>
<span>Du hast noch kein Konto?</span>
</div>
<div className={styles.ctaSection}>
<button
type="button"
className={styles.ctaPrimary}
onClick={() => navigate('/register', { state: location.state })}
>
Kostenlos registrieren
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default Login;