'use client'; import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { Box, TextField, Button, Typography, Paper, InputAdornment, IconButton, Divider, Alert, CircularProgress, Link as MuiLink, } from '@mui/material'; import { Visibility, VisibilityOff, Google, Apple, Fingerprint } from '@mui/icons-material'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { motion } from 'framer-motion'; import * as z from 'zod'; import { useAuth } from '@/lib/auth/AuthContext'; import { MFAVerificationDialog } from '@/components/auth/MFAVerificationDialog'; import { tokenStorage } from '@/lib/utils/tokenStorage'; import { biometricApi } from '@/lib/api/biometric'; import { startAuthentication } from '@simplewebauthn/browser'; import Link from 'next/link'; const loginSchema = z.object({ email: z.string().email('Invalid email address'), password: z.string().min(8, 'Password must be at least 8 characters'), }); type LoginFormData = z.infer; export default function LoginPage() { const [showPassword, setShowPassword] = useState(false); const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(false); const [isBiometricLoading, setIsBiometricLoading] = useState(false); const [isBiometricSupported, setIsBiometricSupported] = useState(false); const [mfaRequired, setMfaRequired] = useState(false); const [mfaData, setMfaData] = useState<{ userId: string; mfaMethod: 'totp' | 'email' } | null>(null); const { login } = useAuth(); const router = useRouter(); const { register, handleSubmit, formState: { errors }, watch, } = useForm({ resolver: zodResolver(loginSchema), }); const email = watch('email'); // Check biometric support on mount useEffect(() => { checkBiometricSupport(); }, []); const checkBiometricSupport = async () => { const supported = biometricApi.isSupported(); if (supported) { const available = await biometricApi.isPlatformAuthenticatorAvailable(); setIsBiometricSupported(available); } }; const handleBiometricLogin = async () => { setError(null); setIsBiometricLoading(true); try { // Get authentication options from server const options = await biometricApi.getAuthenticationOptions(email || undefined); // Start WebAuthn authentication (triggers Face ID/Touch ID/Windows Hello) const authenticationResponse = await startAuthentication(options); // Send response to server for verification and get tokens const result = await biometricApi.verifyAuthentication( authenticationResponse, email || undefined, { deviceId: authenticationResponse.id.substring(0, 10), platform: navigator.userAgent, } ); // Store tokens and navigate tokenStorage.setTokens(result.data.tokens.accessToken, result.data.tokens.refreshToken); router.push('/'); } catch (err: any) { console.error('Biometric login failed:', err); if (err.name === 'NotAllowedError') { setError('Biometric authentication was cancelled'); } else if (err.name === 'NotSupportedError') { setError('Biometric authentication is not supported on this device'); } else { setError(err.response?.data?.message || err.message || 'Biometric login failed. Please try again.'); } } finally { setIsBiometricLoading(false); } }; const onSubmit = async (data: LoginFormData) => { setError(null); setIsLoading(true); try { await login(data); // Navigation is handled in the login function } catch (err: any) { // Check if MFA is required if (err.response?.data?.mfaRequired) { setMfaRequired(true); setMfaData({ userId: err.response.data.userId, mfaMethod: err.response.data.mfaMethod, }); } else { setError(err.message || 'Failed to login. Please check your credentials.'); } } finally { setIsLoading(false); } }; const handleMFAVerified = (tokens: { accessToken: string; refreshToken: string }, user: any) => { // Store tokens and navigate tokenStorage.setTokens(tokens.accessToken, tokens.refreshToken); setMfaRequired(false); router.push('/'); }; const handleMFACancel = () => { setMfaRequired(false); setMfaData(null); }; return ( Welcome Back 👋 Sign in to continue tracking your child's journey {error && ( {error} )} setShowPassword(!showPassword)} edge="end" disabled={isLoading} > {showPassword ? : } ), }} /> Forgot password? OR {isBiometricSupported && ( )} Don't have an account?{' '} Sign up {/* MFA Verification Dialog */} {mfaRequired && mfaData && ( )} ); }