import React, { useState, useRef } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { Turnstile } from '@marsidev/react-turnstile'; import api from '../../api'; import { useAuth } from '../../App'; const LoginPage = () => { const { t } = useTranslation(); const [form, setForm] = useState({ email: '', password: '', website: '' }); const [error, setError] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const [emailTouched, setEmailTouched] = useState(false); const [emailValid, setEmailValid] = useState(null); const [captchaToken, setCaptchaToken] = useState(null); const turnstileRef = useRef(null); // 2FA login state const [requires2fa, setRequires2fa] = useState(false); const [tempToken, setTempToken] = useState(null); const [totpCode, setTotpCode] = useState(''); const navigate = useNavigate(); const { login } = useAuth(); const validateEmail = (email: string) => Boolean(String(email).toLowerCase().match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)); const handleEmailChange = (e: React.ChangeEvent) => { const val = e.target.value; setForm({ ...form, email: val }); if (emailTouched) { setEmailValid(val === '' ? null : validateEmail(val)); } }; const handleEmailBlur = () => { setEmailTouched(true); setEmailValid(form.email === '' ? null : validateEmail(form.email)); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (form.website) return; if (requires2fa) { setIsSubmitting(true); setError(''); try { const res = await api.post('/auth/login/2fa', { temp_token: tempToken, code: totpCode, }); login(res.data.token); navigate('/dashboard'); } catch (err: any) { setError(err.response?.data?.error || 'Invalid 2FA code.'); } finally { setIsSubmitting(false); } return; } setEmailTouched(true); const isEmailValid = validateEmail(form.email); setEmailValid(isEmailValid); if (!isEmailValid) { setError('Please enter a valid email address.'); return; } setIsSubmitting(true); setError(''); let currentToken = captchaToken; if (!currentToken && turnstileRef.current) { try { currentToken = await turnstileRef.current.getResponsePromise(2000); } catch (err) { try { turnstileRef.current.reset(); currentToken = await turnstileRef.current.getResponsePromise(4000); } catch (resetErr) { console.error('Turnstile verification failed:', resetErr); } } } if (!currentToken) { setError('Please complete the security check.'); setIsSubmitting(false); return; } try { const res = await api.post('/auth/login', { email: form.email, password: form.password, captchaToken: currentToken, }); if (res.data.requires_2fa) { setRequires2fa(true); setTempToken(res.data.temp_token); } else { login(res.data.token); navigate('/dashboard'); } } catch (err: any) { setError(err.response?.data?.error || 'Invalid credentials.'); } finally { setIsSubmitting(false); } }; const emailIsInvalid = emailTouched && emailValid === false; const emailIsValid = emailTouched && emailValid === true; return ( <>

Welcome back

Sign in to your PumpkinMC account.

{error && (
{error}
)} {/* Honeypot */} setForm({ ...form, website: e.target.value })} /> {requires2fa ? (

Enter the 6-digit code from your authenticator app.

setTotpCode(e.target.value.replace(/\D/g, '').slice(0, 6))} autoComplete="one-time-code" inputMode="numeric" pattern="[0-9]*" required disabled={isSubmitting} autoFocus style={{ letterSpacing: '0.2em', textAlign: 'center', fontSize: '1.2rem', fontFamily: 'var(--font-mono, monospace)' }} />
) : ( <>
{emailIsInvalid && ( )} {emailIsValid && ( )}
{emailIsInvalid && (

Please enter a valid email address

)}
Forgot password?
setForm({ ...form, password: e.target.value })} required disabled={isSubmitting} autoComplete="current-password" />
)} {!requires2fa && (
setCaptchaToken(token)} onExpire={() => setCaptchaToken(null)} onError={() => setCaptchaToken(null)} options={{ theme: 'light', appearance: 'interaction-only' } as any} />
)}
{t('auth.no_account')} {t('auth.create_account')}
); }; export default LoginPage;