import React, { useState, useEffect } from 'react' import { useAuth } from '../contexts/AuthContext' import { useLanguage } from '../contexts/LanguageContext' import { t } from '../i18n/translations' import { Eye, EyeOff } from 'lucide-react' import { DeepVoidBackground } from './DeepVoidBackground' // import { Input } from './ui/input' // Removed unused import import { toast } from 'sonner' import { useSystemConfig } from '../hooks/useSystemConfig' export function LoginPage() { const { language } = useLanguage() const { login, loginAdmin, verifyOTP, completeRegistration } = useAuth() const [step, setStep] = useState<'login' | 'otp' | 'setup-otp'>('login') const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [showPassword, setShowPassword] = useState(false) const [otpCode, setOtpCode] = useState('') const [userID, setUserID] = useState('') const [qrCodeURL, setQrCodeURL] = useState('') // New state for recovery const [otpSecret, setOtpSecret] = useState('') // New state for recovery const [error, setError] = useState('') const [loading, setLoading] = useState(false) const [adminPassword, setAdminPassword] = useState('') const adminMode = false const { config: systemConfig } = useSystemConfig() const registrationEnabled = systemConfig?.registration_enabled !== false const [expiredToastId, setExpiredToastId] = useState(null) // Show notification if user was redirected here due to 401 useEffect(() => { if (sessionStorage.getItem('from401') === 'true') { const id = toast.warning(t('sessionExpired', language), { duration: Infinity // Keep showing until user dismisses or logs in }) setExpiredToastId(id) sessionStorage.removeItem('from401') } }, [language]) const handleAdminLogin = async (e: React.FormEvent) => { e.preventDefault() setError('') setLoading(true) const result = await loginAdmin(adminPassword) if (!result.success) { const msg = result.message || t('loginFailed', language) setError(msg) toast.error(msg) } else { // Dismiss the "login expired" toast on successful login if (expiredToastId) { toast.dismiss(expiredToastId) } } setLoading(false) } const handleLogin = async (e: React.FormEvent) => { e.preventDefault() setError('') setLoading(true) const result = await login(email, password) if (result.success) { // Check for incomplete OTP setup (user registered but didn't complete 2FA) if (result.requiresOTPSetup && result.userID) { setUserID(result.userID) setQrCodeURL(result.qrCodeURL || '') setOtpSecret(result.otpSecret || '') setStep('setup-otp') toast.info("Pending 2FA setup detected. Please complete configuration.") } else if (result.requiresOTP && result.userID) { setUserID(result.userID) // Check if backend provided recovery data (meaning 2FA is pending setup) if (result.qrCodeURL) { setQrCodeURL(result.qrCodeURL) setOtpSecret(result.otpSecret || '') setStep('setup-otp') toast.info("Pending 2FA setup detected. Please complete configuration.") } else { setStep('otp') } } else { // Dismiss the "login expired" toast on successful login (no OTP required) if (expiredToastId) { toast.dismiss(expiredToastId) } } } else { // Check if we have recovery data despite the error (e.g. "Account has not completed OTP setup") if (result.qrCodeURL) { setUserID(result.userID || '') // We might need to ensure userID is returned in error case too, or derived setQrCodeURL(result.qrCodeURL) setOtpSecret(result.otpSecret || '') setStep('setup-otp') toast.warning(t('completeGapSetup', language) || "Incomplete setup detected. Please configure 2FA.") } else { const msg = result.message || t('loginFailed', language) setError(msg) toast.error(msg) } } setLoading(false) } const handleOTPVerify = async (e: React.FormEvent) => { e.preventDefault() setError('') setLoading(true) // If we have qrCodeURL, it means user needs to complete registration (first time OTP setup) // Otherwise, it's a normal login OTP verification const result = qrCodeURL ? await completeRegistration(userID, otpCode) : await verifyOTP(userID, otpCode) if (!result.success) { const msg = result.message || t('verificationFailed', language) setError(msg) toast.error(msg) } else { // Dismiss the "login expired" toast on successful OTP verification if (expiredToastId) { toast.dismiss(expiredToastId) } // Clear qrCodeURL after successful completion setQrCodeURL('') setOtpSecret('') } // 成功的话AuthContext会自动处理登录状态 setLoading(false) } const copyToClipboard = (text: string) => { navigator.clipboard.writeText(text) toast.success('Copied to clipboard') } return (
{/* Navigation - Top Bar (Mobile/Desktop Friendly) */}
{/* Terminal Header */}
NoFx Logo

SYSTEM ACCESS

{step === 'login' ? 'Authentication Protocol v3.0' : 'Multi-Factor Verification'}

{/* Terminal Output / Form Container */}
{/* Window Bar */}
window.location.href = '/'} title="Close / Return Home" >
login.exe
{/* Status Output */}
Initiating handshake...
Target: NOFX CORE HUB
Status: AWAITING CREDENTIALS
{adminMode ? (
setAdminPassword(e.target.value)} className="w-full bg-black/50 border border-zinc-700 rounded px-4 py-3 text-sm focus:border-nofx-gold focus:ring-1 focus:ring-nofx-gold/50 outline-none transition-all placeholder-zinc-700 text-white font-mono" placeholder="ENTER_ROOT_PASSWORD" required />
{error && (
[ERROR]: {error}
)}
) : step === 'setup-otp' ? (
COMPLETE 2FA CONFIGURATION
{qrCodeURL ? (
QR Code
) : (
)}

Backup Secret Key

{otpSecret}
01

Install Authenticator App

Recommended: Google Authenticator.

iOS Android
02

Scan & Verify

Scan code above, then enter the 6-digit token below to activate your account.

) : step === 'login' ? (
setEmail(e.target.value)} className="w-full bg-black/50 border border-zinc-700 rounded px-4 py-3 text-sm focus:border-nofx-gold focus:ring-1 focus:ring-nofx-gold/50 outline-none transition-all placeholder-zinc-700 text-white font-mono" placeholder="user@nofx.os" required />
setPassword(e.target.value)} className="w-full bg-black/50 border border-zinc-700 rounded px-4 py-3 text-sm focus:border-nofx-gold focus:ring-1 focus:ring-nofx-gold/50 outline-none transition-all placeholder-zinc-700 text-white font-mono pr-10" placeholder="••••••••••••" required />
{error && (
{error}
)}
) : (
🔐

{t('scanQRCodeInstructions', language)}
{t('enterOTPCode', language)}

setOtpCode(e.target.value.replace(/\D/g, '').slice(0, 6)) } className="w-full bg-black border border-zinc-700 rounded px-4 py-4 text-center text-2xl tracking-[0.5em] font-mono text-white focus:border-nofx-gold focus:ring-1 focus:ring-nofx-gold/50 outline-none transition-all placeholder-zinc-800" placeholder="000000" maxLength={6} required autoFocus />
{error && (
[ACCESS DENIED]: {error}
)}
)}
{/* Terminal Footer Info */}
SECURE_CONNECTION: ENCRYPTED
{new Date().toISOString().split('T')[0]}
{/* Register Link */} {!adminMode && registrationEnabled && (

NEW_USER_DETECTED?{' '}

)}
) }