import React, { useState, useEffect } from 'react'; import { Box, Card, CardContent, Typography, TextField, Button, Alert, CircularProgress, Link, Divider, InputAdornment, Dialog, DialogTitle, DialogContent, DialogActions, Checkbox, FormControlLabel, Grid } from '@mui/material'; import { Email as EmailIcon, Security as SecurityIcon, CheckCircle as CheckCircleIcon, ErrorOutline as ErrorIcon, Refresh as RefreshIcon, DevicesOther as DevicesIcon } from '@mui/icons-material'; import { useAuth } from 'hooks/AuthContext'; import { BackstoryPageProps } from './BackstoryTab'; import { useNavigate } from 'react-router-dom'; // Email Verification Component const EmailVerificationPage = (props: BackstoryPageProps) => { const { verifyEmail, resendEmailVerification, getPendingVerificationEmail, isLoading, error } = useAuth(); const navigate = useNavigate(); const [verificationToken, setVerificationToken] = useState(''); const [status, setStatus] = useState<'pending' | 'success' | 'error'>('pending'); const [message, setMessage] = useState(''); const [userType, setUserType] = useState(''); useEffect(() => { // Get token from URL parameters const urlParams = new URLSearchParams(window.location.search); const token = urlParams.get('token'); if (token) { setVerificationToken(token); handleVerifyEmail(token); } }, []); const handleVerifyEmail = async (token: string) => { if (!token) { setStatus('error'); setMessage('Invalid verification link'); return; } try { const result = await verifyEmail({ token }); if (result) { setStatus('success'); setMessage(result.message); setUserType(result.userType); // Redirect to login after 3 seconds setTimeout(() => { navigate('/login'); }, 3000); } else { setStatus('error'); setMessage('Email verification failed'); } } catch (error) { setStatus('error'); setMessage('Email verification failed'); } }; const handleResendVerification = async () => { const email = getPendingVerificationEmail(); if (!email) { setMessage('No pending verification email found.'); return; } try { const success = await resendEmailVerification(email); if (success) { setMessage('Verification email sent successfully!'); } } catch (error) { setMessage('Failed to resend verification email.'); } }; return ( {status === 'pending' && ( <> Verifying Email Please wait while we verify your email address... )} {status === 'success' && ( <> Email Verified! Your {userType} account has been successfully activated. )} {status === 'error' && ( <> Verification Failed We couldn't verify your email address. )} {isLoading && ( )} {(message || error) && ( {message || error} )} {status === 'success' && ( You will be redirected to the login page in a few seconds... )} {status === 'error' && ( )} ); } // MFA Verification Component interface MFAVerificationDialogProps { open: boolean; onClose: () => void; onVerificationSuccess: (authData: any) => void; } const MFAVerificationDialog = (props: MFAVerificationDialogProps) => { const { open, onClose, onVerificationSuccess } = props; const { verifyMFA, resendMFACode, clearMFA, mfaResponse, isLoading, error } = useAuth(); const [code, setCode] = useState(''); const [rememberDevice, setRememberDevice] = useState(false); const [localError, setLocalError] = useState(''); const [timeLeft, setTimeLeft] = useState(600); // 10 minutes in seconds const [errorMessage, setErrorMessage] = useState(null); useEffect(() => { if (!error) { return; } /* Remove 'HTTP .*: ' from error string */ const jsonStr = error.replace(/^[^{]*/, ''); const data = JSON.parse(jsonStr); setErrorMessage(data.error.message); }, [error]); useEffect(() => { if (!open) return; const timer = setInterval(() => { setTimeLeft((prev) => { if (prev <= 1) { clearInterval(timer); setLocalError('MFA code has expired. Please try logging in again.'); return 0; } return prev - 1; }); }, 1000); return () => clearInterval(timer); }, [open]); const formatTime = (seconds: number) => { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins}:${secs.toString().padStart(2, '0')}`; }; const handleVerifyMFA = async () => { if (!code || code.length !== 6) { setLocalError('Please enter a valid 6-digit code'); return; } if (!mfaResponse || !mfaResponse.mfaData) { setLocalError('MFA data not available'); return; } setLocalError(''); try { const success = await verifyMFA({ email: mfaResponse.mfaData.email, code, deviceId: mfaResponse.mfaData.deviceId, rememberDevice, }); if (success) { onVerificationSuccess({ success: true }); onClose(); } } catch (error) { setLocalError('Verification failed. Please try again.'); } }; const handleResendCode = async () => { if (!mfaResponse || !mfaResponse.mfaData) { setLocalError('MFA data not available'); return; } try { const success = await resendMFACode(mfaResponse.mfaData.email, mfaResponse.mfaData.deviceId, mfaResponse.mfaData.deviceName); if (success) { setTimeLeft(600); // Reset timer setLocalError(''); alert('New verification code sent to your email'); } } catch (error) { setLocalError('Failed to resend code'); } }; const handleClose = () => { clearMFA(); onClose(); }; if (!mfaResponse || !mfaResponse.mfaData) return null; return ( Verify Your Identity We've detected a login from a new device: {mfaResponse.mfaData.deviceName} We've sent a 6-digit verification code to: {mfaResponse.mfaData.email} { const value = e.target.value.replace(/\D/g, '').slice(0, 6); setCode(value); setLocalError(''); }} placeholder="000000" inputProps={{ maxLength: 6, style: { fontSize: 24, textAlign: 'center', letterSpacing: 8 } }} sx={{ mt: 2, mb: 2 }} error={!!(localError || errorMessage)} helperText={localError || errorMessage} /> Code expires in: {formatTime(timeLeft)} setRememberDevice(e.target.checked)} /> } label="Remember this device for 90 days" /> If you didn't attempt to log in, please change your password immediately. ); } // Enhanced Registration Success Component const RegistrationSuccessDialog = ({ open, onClose, email, userType }: { open: boolean; onClose: () => void; email: string; userType: string; }) => { const { resendEmailVerification, isLoading } = useAuth(); const [resendMessage, setResendMessage] = useState(''); const handleResendVerification = async () => { try { const success = await resendEmailVerification(email); if (success) { setResendMessage('Verification email sent!'); } } catch (error: any) { setResendMessage(error?.message || 'Network error. Please try again.'); } }; return ( Check Your Email We've sent a verification link to: {email} Next steps:
1. Check your email inbox (and spam folder)
2. Click the verification link
3. Your {userType} account will be activated
{resendMessage && ( {resendMessage} )}
); } // Enhanced Login Component with MFA Support const LoginForm = () => { const { login, mfaResponse, isLoading, error } = useAuth(); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [errorMessage, setErrorMessage] = useState(null); useEffect(() => { if (!error) { return; } /* Remove 'HTTP .*: ' from error string */ const jsonStr = error.replace(/^[^{]*/, ''); const data = JSON.parse(jsonStr); setErrorMessage(data.error.message); }, [error]); const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); const success = await login({ login: email, password }); console.log(`login success: ${success}`); if (success) { // Redirect based on user type - this could be handled in AuthContext // or by a higher-level component that listens to auth state changes handleLoginSuccess(); } }; const handleMFASuccess = (authData: any) => { handleLoginSuccess(); }; const handleLoginSuccess = () => { // This could be handled by a router or parent component // For now, just showing the pattern console.log('Login successful - redirect to dashboard'); }; return ( setEmail(e.target.value)} autoComplete="email" autoFocus /> setPassword(e.target.value)} autoComplete="current-password" /> {errorMessage && ( {errorMessage} )} {/* MFA Dialog */} { }} // This will be handled by clearMFA in the dialog onVerificationSuccess={handleMFASuccess} /> ); } // Device Management Component const TrustedDevicesManager = () => { const [devices, setDevices] = useState([]); const [loading, setLoading] = useState(true); // This would need API endpoints to manage trusted devices useEffect(() => { // Load trusted devices setLoading(false); }, []); return ( Trusted Devices Manage devices that you've marked as trusted. You won't need to verify your identity when signing in from these devices. {devices.length === 0 ? ( No trusted devices yet. When you log in from a new device and choose to remember it, it will appear here. ) : ( {devices.map((device, index) => ( {device.deviceName} Added: {new Date(device.addedAt).toLocaleDateString()} Last used: {new Date(device.lastUsed).toLocaleDateString()} ))} )} ); } export { EmailVerificationPage, MFAVerificationDialog, TrustedDevicesManager, RegistrationSuccessDialog, LoginForm };