US-028: Change login username to a.recruiter and add connection status indicator
This commit is contained in:
@@ -61,9 +61,9 @@ export function DashboardLayout() {
|
|||||||
setCommandPaletteOpen(false)
|
setCommandPaletteOpen(false)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const handleSectionClick = useCallback((_sectionId: string) => {
|
const handleSectionClick = useCallback((_sectionId: string) => {
|
||||||
// Section click is already handled in SubNav component
|
// SubNav handles scrolling internally
|
||||||
// This is just a placeholder for any additional logic needed
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// Global Ctrl+K listener to open command palette
|
// Global Ctrl+K listener to open command palette
|
||||||
|
|||||||
@@ -16,9 +16,10 @@ export function LoginScreen({ onComplete }: LoginScreenProps) {
|
|||||||
const [isExiting, setIsExiting] = useState(false)
|
const [isExiting, setIsExiting] = useState(false)
|
||||||
const [typingComplete, setTypingComplete] = useState(false)
|
const [typingComplete, setTypingComplete] = useState(false)
|
||||||
const [buttonHovered, setButtonHovered] = useState(false)
|
const [buttonHovered, setButtonHovered] = useState(false)
|
||||||
|
const [connectionState, setConnectionState] = useState<'connecting' | 'connected'>('connecting')
|
||||||
const { requestFocusAfterLogin } = useAccessibility()
|
const { requestFocusAfterLogin } = useAccessibility()
|
||||||
|
|
||||||
const fullUsername = 'A.CHARLWOOD'
|
const fullUsername = 'a.recruiter'
|
||||||
const passwordLength = 8
|
const passwordLength = 8
|
||||||
|
|
||||||
const prefersReducedMotion = typeof window !== 'undefined'
|
const prefersReducedMotion = typeof window !== 'undefined'
|
||||||
@@ -38,8 +39,10 @@ export function LoginScreen({ onComplete }: LoginScreenProps) {
|
|||||||
return id
|
return id
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const canLogin = typingComplete && connectionState === 'connected'
|
||||||
|
|
||||||
const handleLogin = useCallback(() => {
|
const handleLogin = useCallback(() => {
|
||||||
if (!typingComplete || isExiting) return
|
if (!canLogin || isExiting) return
|
||||||
setButtonPressed(true)
|
setButtonPressed(true)
|
||||||
addTimeout(() => {
|
addTimeout(() => {
|
||||||
setIsExiting(true)
|
setIsExiting(true)
|
||||||
@@ -48,7 +51,7 @@ export function LoginScreen({ onComplete }: LoginScreenProps) {
|
|||||||
onComplete()
|
onComplete()
|
||||||
}, prefersReducedMotion ? 0 : 200)
|
}, prefersReducedMotion ? 0 : 200)
|
||||||
}, 100)
|
}, 100)
|
||||||
}, [typingComplete, isExiting, onComplete, requestFocusAfterLogin, prefersReducedMotion, addTimeout])
|
}, [canLogin, isExiting, onComplete, requestFocusAfterLogin, prefersReducedMotion, addTimeout])
|
||||||
|
|
||||||
const startLoginSequence = useCallback(() => {
|
const startLoginSequence = useCallback(() => {
|
||||||
if (prefersReducedMotion) {
|
if (prefersReducedMotion) {
|
||||||
@@ -93,12 +96,12 @@ export function LoginScreen({ onComplete }: LoginScreenProps) {
|
|||||||
}, 80)
|
}, 80)
|
||||||
}, [prefersReducedMotion, addTimeout])
|
}, [prefersReducedMotion, addTimeout])
|
||||||
|
|
||||||
// Focus the login button when typing completes for keyboard accessibility
|
// Focus the login button when login becomes available for keyboard accessibility
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typingComplete && loginButtonRef.current) {
|
if (canLogin && loginButtonRef.current) {
|
||||||
loginButtonRef.current.focus()
|
loginButtonRef.current.focus()
|
||||||
}
|
}
|
||||||
}, [typingComplete])
|
}, [canLogin])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Cursor blink: 530ms interval
|
// Cursor blink: 530ms interval
|
||||||
@@ -106,6 +109,11 @@ export function LoginScreen({ onComplete }: LoginScreenProps) {
|
|||||||
setShowCursor(prev => !prev)
|
setShowCursor(prev => !prev)
|
||||||
}, 530)
|
}, 530)
|
||||||
|
|
||||||
|
// Connection status: transitions to connected after ~2000ms
|
||||||
|
const connectionTimeout = addTimeout(() => {
|
||||||
|
setConnectionState('connected')
|
||||||
|
}, 2000)
|
||||||
|
|
||||||
// Delay start slightly for card entrance animation
|
// Delay start slightly for card entrance animation
|
||||||
const startTimeout = addTimeout(() => {
|
const startTimeout = addTimeout(() => {
|
||||||
startLoginSequence()
|
startLoginSequence()
|
||||||
@@ -119,13 +127,14 @@ export function LoginScreen({ onComplete }: LoginScreenProps) {
|
|||||||
if (usernameIntervalRef.current) clearInterval(usernameIntervalRef.current)
|
if (usernameIntervalRef.current) clearInterval(usernameIntervalRef.current)
|
||||||
if (passwordIntervalRef.current) clearInterval(passwordIntervalRef.current)
|
if (passwordIntervalRef.current) clearInterval(passwordIntervalRef.current)
|
||||||
clearTimeout(startTimeout)
|
clearTimeout(startTimeout)
|
||||||
|
clearTimeout(connectionTimeout)
|
||||||
pendingTimeouts.forEach(id => clearTimeout(id))
|
pendingTimeouts.forEach(id => clearTimeout(id))
|
||||||
}
|
}
|
||||||
}, [startLoginSequence, addTimeout])
|
}, [startLoginSequence, addTimeout])
|
||||||
|
|
||||||
const buttonBg = buttonPressed
|
const buttonBg = buttonPressed
|
||||||
? '#085858'
|
? '#085858'
|
||||||
: buttonHovered && typingComplete
|
: buttonHovered && canLogin
|
||||||
? '#0A8080'
|
? '#0A8080'
|
||||||
: '#0D6E6E'
|
: '#0D6E6E'
|
||||||
|
|
||||||
@@ -284,7 +293,7 @@ export function LoginScreen({ onComplete }: LoginScreenProps) {
|
|||||||
<button
|
<button
|
||||||
ref={loginButtonRef}
|
ref={loginButtonRef}
|
||||||
onClick={handleLogin}
|
onClick={handleLogin}
|
||||||
disabled={!typingComplete}
|
disabled={!canLogin}
|
||||||
onMouseEnter={() => setButtonHovered(true)}
|
onMouseEnter={() => setButtonHovered(true)}
|
||||||
onMouseLeave={() => setButtonHovered(false)}
|
onMouseLeave={() => setButtonHovered(false)}
|
||||||
className="focus-visible:ring-2 focus-visible:ring-[#0D6E6E]/40 focus-visible:ring-offset-2 focus:outline-none"
|
className="focus-visible:ring-2 focus-visible:ring-[#0D6E6E]/40 focus-visible:ring-offset-2 focus:outline-none"
|
||||||
@@ -298,13 +307,47 @@ export function LoginScreen({ onComplete }: LoginScreenProps) {
|
|||||||
backgroundColor: buttonBg,
|
backgroundColor: buttonBg,
|
||||||
border: 'none',
|
border: 'none',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
cursor: typingComplete ? 'pointer' : 'default',
|
cursor: canLogin ? 'pointer' : 'default',
|
||||||
opacity: typingComplete ? 1 : 0.6,
|
opacity: canLogin ? 1 : 0.6,
|
||||||
transition: 'background-color 150ms, opacity 300ms',
|
transition: 'background-color 150ms, opacity 300ms',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Log In
|
Log In
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{/* Connection Status Indicator */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: '6px',
|
||||||
|
marginTop: '4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
width: '6px',
|
||||||
|
height: '6px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: connectionState === 'connected' ? '#059669' : '#DC2626',
|
||||||
|
transition: 'background-color 300ms ease',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontFamily: "var(--font-geist-mono, 'Geist Mono', monospace)",
|
||||||
|
fontSize: '10px',
|
||||||
|
color: connectionState === 'connected' ? '#059669' : '#8DA8A5',
|
||||||
|
transition: 'color 300ms ease',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{connectionState === 'connected'
|
||||||
|
? 'Secure connection established'
|
||||||
|
: 'Awaiting secure connection...'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
|
|||||||
Reference in New Issue
Block a user