import { useState, useEffect, useCallback, useRef } from 'react' import { motion } from 'framer-motion' import { CvmisLogo } from './CvmisLogo' import { useAccessibility } from '../contexts/AccessibilityContext' interface LoginScreenProps { onComplete: () => void } export function LoginScreen({ onComplete }: LoginScreenProps) { const [username, setUsername] = useState('') const [passwordDots, setPasswordDots] = useState(0) const [showCursor, setShowCursor] = useState(true) const [activeField, setActiveField] = useState<'username' | 'password' | 'done' | null>('username') const [buttonPressed, setButtonPressed] = useState(false) const [isExiting, setIsExiting] = useState(false) const [isLoading, setIsLoading] = useState(false) const [typingComplete, setTypingComplete] = useState(false) const [buttonHovered, setButtonHovered] = useState(false) const [connectionState, setConnectionState] = useState<'connecting' | 'connected'>('connecting') const [dotCount, setDotCount] = useState(0) const { requestFocusAfterLogin } = useAccessibility() const fullUsername = 'a.recruiter' const passwordLength = 8 const prefersReducedMotion = typeof window !== 'undefined' ? window.matchMedia('(prefers-reduced-motion: reduce)').matches : false // Refs for interval/timeout cleanup const usernameIntervalRef = useRef | null>(null) const passwordIntervalRef = useRef | null>(null) const cursorIntervalRef = useRef | null>(null) const timeoutRefs = useRef[]>([]) const dotIntervalRef = useRef | null>(null) const loginButtonRef = useRef(null) const addTimeout = useCallback((fn: () => void, delay: number) => { const id = setTimeout(fn, delay) timeoutRefs.current.push(id) return id }, []) const canLogin = typingComplete && connectionState === 'connected' const handleLogin = useCallback(() => { if (!canLogin || isExiting || isLoading) return setButtonPressed(true) addTimeout(() => { setIsLoading(true) addTimeout(() => { setIsExiting(true) // After dissolve completes (~600ms), remove overlay and reveal dashboard addTimeout(() => { requestFocusAfterLogin() onComplete() }, prefersReducedMotion ? 0 : 600) }, prefersReducedMotion ? 0 : 600) }, 100) }, [canLogin, isExiting, isLoading, onComplete, requestFocusAfterLogin, prefersReducedMotion, addTimeout]) const startLoginSequence = useCallback(() => { if (prefersReducedMotion) { setUsername(fullUsername) setPasswordDots(passwordLength) setActiveField('done') setTypingComplete(true) // Button is immediately available for user to click return } // Username typing: 80ms per character let usernameIndex = 0 usernameIntervalRef.current = setInterval(() => { if (usernameIndex <= fullUsername.length) { setUsername(fullUsername.slice(0, usernameIndex)) usernameIndex++ } else { if (usernameIntervalRef.current) { clearInterval(usernameIntervalRef.current) } setActiveField('password') // Password dots: 60ms per dot, after 300ms pause addTimeout(() => { let dotCount = 0 passwordIntervalRef.current = setInterval(() => { if (dotCount <= passwordLength) { setPasswordDots(dotCount) dotCount++ } else { if (passwordIntervalRef.current) { clearInterval(passwordIntervalRef.current) } setActiveField('done') setTypingComplete(true) // Button becomes interactive — user clicks to proceed } }, 60) }, 300) } }, 80) }, [prefersReducedMotion, addTimeout]) // Focus the login button when login becomes available for keyboard accessibility useEffect(() => { if (canLogin && loginButtonRef.current) { loginButtonRef.current.focus() } }, [canLogin]) // Connection transitions to green 500ms after typing completes useEffect(() => { if (!typingComplete) return const timeout = addTimeout(() => { setConnectionState('connected') }, prefersReducedMotion ? 0 : 500) return () => clearTimeout(timeout) }, [typingComplete, addTimeout, prefersReducedMotion]) // Animated trailing dots while connecting useEffect(() => { if (connectionState === 'connected' || prefersReducedMotion) { if (dotIntervalRef.current) clearInterval(dotIntervalRef.current) return } dotIntervalRef.current = setInterval(() => { setDotCount(prev => (prev + 1) % 4) }, 500) return () => { if (dotIntervalRef.current) clearInterval(dotIntervalRef.current) } }, [connectionState, prefersReducedMotion]) useEffect(() => { // Cursor blink: 530ms interval cursorIntervalRef.current = setInterval(() => { setShowCursor(prev => !prev) }, 530) // Delay start to allow card entrance + logo animation to complete // Reduced motion: logo shows instantly, so use original 400ms delay // Full motion: 400ms card entrance + 1000ms logo animation + 100ms pause = 1500ms const startTimeout = addTimeout(() => { startLoginSequence() }, prefersReducedMotion ? 400 : 1500) // Capture ref value for cleanup const pendingTimeouts = timeoutRefs.current return () => { if (cursorIntervalRef.current) clearInterval(cursorIntervalRef.current) if (usernameIntervalRef.current) clearInterval(usernameIntervalRef.current) if (passwordIntervalRef.current) clearInterval(passwordIntervalRef.current) if (dotIntervalRef.current) clearInterval(dotIntervalRef.current) clearTimeout(startTimeout) pendingTimeouts.forEach(id => clearTimeout(id)) } }, [startLoginSequence, addTimeout, prefersReducedMotion]) const buttonBg = buttonPressed ? '#085858' : buttonHovered && canLogin ? '#0A8080' : '#0D6E6E' return ( {isLoading ? (
Loading clinical records...
) : ( <> {/* Branding Header */}
CVMIS CV Management Information System
{/* Login Form */}
{/* Username Field */}
{username} {activeField === 'username' && ( )}
{/* Password Field */}
{'\u2022'.repeat(passwordDots)} {activeField === 'password' && ( )}
{/* Log In Button — user clicks to proceed */} {/* Connection Status Indicator */}
{connectionState === 'connected' ? 'Secure connection established, awaiting login' : `Awaiting secure connection${'.'.repeat(dotCount)}`}
{/* Footer */}

Secure clinical system login

)} ) }