import { createFileRoute, Link, useNavigate } from '@tanstack/react-router' import { useState, useEffect } from 'react' import { useAuth } from '@/lib/auth' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { SecretKeyInput } from '@/components/auth/SecretKeyInput' import { SrpUpgradeDialog } from '@/components/auth/SrpUpgradeDialog' import { RugLogo } from '@/components/brand/RugLogo' export const Route = createFileRoute('/login')({ component: LoginPage, }) function LoginPage() { const { login, srpLogin, isAuthenticated, isLoading, error, clearError, needsSecretKey, isDerivingKeys, clearNeedsSecretKey, isUpgrading, pendingUpgradeEmail, pendingUpgradePassword, completeUpgrade, cancelUpgrade, } = useAuth() const navigate = useNavigate() const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [secretKey, setSecretKey] = useState('') const [submitting, setSubmitting] = useState(false) // Redirect if already authenticated useEffect(() => { if (isAuthenticated && !isLoading) { void navigate({ to: '/' }) } }, [isAuthenticated, isLoading, navigate]) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!email || !password) return setSubmitting(true) try { if (needsSecretKey) { // SRP user providing Secret Key on new device await srpLogin(email, password, secretKey) } else { // Normal login -- will auto-redirect to SRP if user is SRP-enrolled await login(email, password) } // Don't navigate if SRP upgrade or Secret Key entry is needed const { isUpgrading: upgrading, needsSecretKey: needsKey } = useAuth.getState() if (!upgrading && !needsKey) { void navigate({ to: '/' }) } } catch { // error handled by useAuth } finally { setSubmitting(false) } } const handleChange = () => { if (error) clearError() } const handleEmailChange = (e: React.ChangeEvent) => { const newEmail = e.target.value // Only clear Secret Key state if email actually changed to a different address if (needsSecretKey && newEmail.toLowerCase() !== email.toLowerCase()) { clearNeedsSecretKey() setSecretKey('') } setEmail(newEmail) handleChange() } const buttonText = () => { if (isDerivingKeys) return 'Unlocking your vault...' if (submitting) return 'Signing in...' if (needsSecretKey) return 'Unlock' return 'Sign In' } return (
{/* Decorative rug border accent */}
{/* Logo / branding */}

TOD - The Other Dude

MSP Fleet Management

{/* Login card */}
void handleSubmit(e)} className="space-y-4">
Forgot password?
{ setPassword(e.target.value) handleChange() }} placeholder="--------" autoComplete="current-password" required data-testid="input-password" />
{/* Secret Key input -- shown when SRP user on new device */} {needsSecretKey && (
From your Emergency Kit
{ setSecretKey(v) handleChange() }} error={!!error} />

This device does not have your Secret Key stored. Enter it from your Emergency Kit to unlock your vault.

Lost your Secret Key?

Use{' '} Forgot password {' '} to reset your account. You will set a new password and receive a new Secret Key. Note: previously encrypted data may be inaccessible.

)} {error && (

{error}

)}
{/* First-run hint (dev only) */} {import.meta.env.DEV && (

First time? Use the credentials from your .env file (FIRST_ADMIN_EMAIL / FIRST_ADMIN_PASSWORD).

)} {/* Legal links */}
Terms of Service Privacy Policy
{/* SRP Upgrade Dialog for legacy bcrypt users */} {isUpgrading && pendingUpgradeEmail && pendingUpgradePassword && ( { await completeUpgrade() void navigate({ to: '/' }) }} onCancel={cancelUpgrade} /> )}
) }