feat: enhance login view with session verification and loading state

- Implemented session verification on component mount using exponential backoff to handle server live reload scenarios.
- Added loading state to the login view while checking for an existing session, improving user experience.
- Removed unused setup wizard navigation from the API keys section for cleaner code.
This commit is contained in:
webdevcody
2026-01-07 10:18:06 -05:00
parent 11accac5ae
commit 0d206fe75f
2 changed files with 64 additions and 22 deletions

View File

@@ -3,17 +3,26 @@
*
* Prompts user to enter the API key shown in server console.
* On successful login, sets an HTTP-only session cookie.
*
* On mount, verifies if an existing session is valid using exponential backoff.
* This handles cases where server live reloads kick users back to login
* even though their session is still valid.
*/
import { useState } from 'react';
import { useState, useEffect, useRef } from 'react';
import { useNavigate } from '@tanstack/react-router';
import { login } from '@/lib/http-api-client';
import { login, verifySession } from '@/lib/http-api-client';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { KeyRound, AlertCircle, Loader2 } from 'lucide-react';
import { useAuthStore } from '@/store/auth-store';
import { useSetupStore } from '@/store/setup-store';
/**
* Delay helper for exponential backoff
*/
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
export function LoginView() {
const navigate = useNavigate();
const setAuthState = useAuthStore((s) => s.setAuthState);
@@ -21,6 +30,45 @@ export function LoginView() {
const [apiKey, setApiKey] = useState('');
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [isCheckingSession, setIsCheckingSession] = useState(true);
const sessionCheckRef = useRef(false);
// Check for existing valid session on mount with exponential backoff
useEffect(() => {
// Prevent duplicate checks in strict mode
if (sessionCheckRef.current) return;
sessionCheckRef.current = true;
const checkExistingSession = async () => {
const maxRetries = 5;
const baseDelay = 500; // Start with 500ms
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const isValid = await verifySession();
if (isValid) {
// Session is valid, redirect to the main app
setAuthState({ isAuthenticated: true, authChecked: true });
navigate({ to: setupComplete ? '/' : '/setup' });
return;
}
// Session is invalid, no need to retry - show login form
break;
} catch {
// Network error or server not ready, retry with exponential backoff
if (attempt < maxRetries - 1) {
const waitTime = baseDelay * Math.pow(2, attempt); // 500, 1000, 2000, 4000, 8000ms
await delay(waitTime);
}
}
}
// Session check complete (either invalid or all retries exhausted)
setIsCheckingSession(false);
};
checkExistingSession();
}, [navigate, setAuthState, setupComplete]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
@@ -45,6 +93,18 @@ export function LoginView() {
}
};
// Show loading state while checking existing session
if (isCheckingSession) {
return (
<div className="flex min-h-screen items-center justify-center bg-background p-4">
<div className="text-center space-y-4">
<Loader2 className="h-8 w-8 animate-spin mx-auto text-primary" />
<p className="text-sm text-muted-foreground">Checking session...</p>
</div>
</div>
);
}
return (
<div className="flex min-h-screen items-center justify-center bg-background p-4">
<div className="w-full max-w-md space-y-8">