import React, { useState, useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import { X, Eye, EyeOff, LogIn, Loader2, Mail, Lock, Shield, ArrowLeft } from 'lucide-react'; import { useNavigate } from 'react-router-dom'; import useAuthStore from '../../../store/useAuthStore'; import { loginSchema, LoginFormData } from '../../../shared/utils/validationSchemas'; import { useCompanySettings } from '../../../shared/contexts/CompanySettingsContext'; import { useAuthModal } from '../contexts/AuthModalContext'; import * as yup from 'yup'; import { toast } from 'react-toastify'; import Recaptcha from '../../../shared/components/Recaptcha'; import { recaptchaService } from '../../../features/system/services/systemSettingsService'; import { useAntibotForm } from '../hooks/useAntibotForm'; import HoneypotField from '../../../shared/components/HoneypotField'; const mfaTokenSchema = yup.object().shape({ mfaToken: yup .string() .required('MFA token is required') .min(6, 'MFA token must be 6 digits') .max(8, 'MFA token must be 6-8 characters') .matches(/^\d+$|^[A-Z0-9]{8}$/, 'Invalid token format'), }); type MFATokenFormData = yup.InferType; const LoginModal: React.FC = () => { const { closeModal, openModal } = useAuthModal(); const { login, verifyMFA, isLoading, error, clearError, requiresMFA, clearMFA, isAuthenticated, userInfo } = useAuthStore(); const { settings } = useCompanySettings(); const navigate = useNavigate(); const [showPassword, setShowPassword] = useState(false); // Enhanced antibot protection const { honeypotValue, setHoneypotValue, recaptchaToken, setRecaptchaToken, validate: validateAntibot, rateLimitInfo, } = useAntibotForm({ formId: 'login', minTimeOnPage: 3000, minTimeToFill: 2000, requireRecaptcha: false, maxAttempts: 5, onValidationError: (errors) => { errors.forEach((err) => toast.error(err)); }, }); const { register: registerMFA, handleSubmit: handleSubmitMFA, formState: { errors: mfaErrors }, } = useForm({ resolver: yupResolver(mfaTokenSchema), defaultValues: { mfaToken: '', }, }); // Close modal and redirect to appropriate dashboard on successful authentication useEffect(() => { if (!isLoading && isAuthenticated && !requiresMFA && userInfo) { closeModal(); // Redirect to role-specific dashboard const role = userInfo.role?.toLowerCase() || (userInfo as any).role_name?.toLowerCase(); if (role === 'admin') { navigate('/admin/dashboard', { replace: true }); } else if (role === 'staff') { navigate('/staff/dashboard', { replace: true }); } else if (role === 'accountant') { navigate('/accountant/dashboard', { replace: true }); } else if (role === 'housekeeping') { navigate('/housekeeping/dashboard', { replace: true }); } else { // Customer or default - go to customer dashboard navigate('/dashboard', { replace: true }); } } }, [isLoading, isAuthenticated, requiresMFA, userInfo, closeModal, navigate]); const { register, handleSubmit, formState: { errors }, } = useForm({ resolver: yupResolver(loginSchema), defaultValues: { email: '', password: '', rememberMe: false, }, }); const onSubmit = async (data: LoginFormData) => { try { clearError(); // Validate antibot protection const isValid = await validateAntibot(); if (!isValid) { return; } // Verify reCAPTCHA if token is provided if (recaptchaToken) { try { const verifyResponse = await recaptchaService.verifyRecaptcha(recaptchaToken); if (verifyResponse.status === 'error' || !verifyResponse.data.verified) { toast.error('reCAPTCHA verification failed. Please try again.'); setRecaptchaToken(null); return; } } catch (error) { toast.error('reCAPTCHA verification failed. Please try again.'); setRecaptchaToken(null); return; } } await login({ email: data.email, password: data.password, rememberMe: data.rememberMe, }); setRecaptchaToken(null); } catch (error) { console.error('Login error:', error); setRecaptchaToken(null); } }; const onSubmitMFA = async (data: MFATokenFormData) => { try { clearError(); await verifyMFA(data.mfaToken); } catch (error) { console.error('MFA verification error:', error); } }; const handleBackToLogin = () => { clearMFA(); clearError(); }; // Handle escape key useEffect(() => { const handleEscape = (e: KeyboardEvent) => { if (e.key === 'Escape') { closeModal(); } }; document.addEventListener('keydown', handleEscape); return () => document.removeEventListener('keydown', handleEscape); }, [closeModal]); return (
{ if (e.target === e.currentTarget) { closeModal(); } }} > {/* Backdrop */}
{/* Modal */}
{/* Close button */}
{/* Header */}
{settings.company_logo_url ? ( {settings.company_name ) : (
)}
{settings.company_tagline && (

{settings.company_tagline}

)}

{requiresMFA ? 'Verify Your Identity' : 'Welcome Back'}

{requiresMFA ? 'Enter the 6-digit code from your authenticator app' : `Sign in to ${settings.company_name || 'Luxury Hotel'}`}

{requiresMFA ? (
{error && (
{error}
)}
{mfaErrors.mfaToken && (

{mfaErrors.mfaToken.message}

)}

Enter the 6-digit code from your authenticator app or an 8-character backup code

) : (
{/* Honeypot field - hidden from users */} {error && (
{error}
)} {rateLimitInfo && !rateLimitInfo.allowed && (

Too many login attempts.

Please try again after {new Date(rateLimitInfo.resetTime).toLocaleTimeString()} {' '}({Math.ceil((rateLimitInfo.resetTime - Date.now()) / 60000)} minutes)

)}
{errors.email && (

{errors.email.message}

)}
{errors.password && (

{errors.password.message}

)}
setRecaptchaToken(token)} onError={(error) => { console.error('reCAPTCHA error:', error); setRecaptchaToken(null); }} theme="light" size="normal" />
)} {!requiresMFA && (

Don't have an account?{' '}

)}
); }; export default LoginModal;