mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 08:33:36 +00:00
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:
@@ -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">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { useSetupStore } from '@/store/setup-store';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Key, CheckCircle2, Settings, Trash2, Loader2 } from 'lucide-react';
|
||||
import { Key, CheckCircle2, Trash2, Loader2 } from 'lucide-react';
|
||||
import { ApiKeyField } from './api-key-field';
|
||||
import { buildProviderConfigs } from '@/config/api-providers';
|
||||
import { SecurityNotice } from './security-notice';
|
||||
@@ -10,13 +10,11 @@ import { cn } from '@/lib/utils';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { toast } from 'sonner';
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
|
||||
export function ApiKeysSection() {
|
||||
const { apiKeys, setApiKeys } = useAppStore();
|
||||
const { claudeAuthStatus, setClaudeAuthStatus, setSetupComplete } = useSetupStore();
|
||||
const { claudeAuthStatus, setClaudeAuthStatus } = useSetupStore();
|
||||
const [isDeletingAnthropicKey, setIsDeletingAnthropicKey] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { providerConfigParams, handleSave, saved } = useApiKeyManagement();
|
||||
|
||||
@@ -51,12 +49,6 @@ export function ApiKeysSection() {
|
||||
}
|
||||
}, [apiKeys, setApiKeys, claudeAuthStatus, setClaudeAuthStatus]);
|
||||
|
||||
// Open setup wizard
|
||||
const openSetupWizard = useCallback(() => {
|
||||
setSetupComplete(false);
|
||||
navigate({ to: '/setup' });
|
||||
}, [setSetupComplete, navigate]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -111,16 +103,6 @@ export function ApiKeysSection() {
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={openSetupWizard}
|
||||
variant="outline"
|
||||
className="h-10 border-border"
|
||||
data-testid="run-setup-wizard"
|
||||
>
|
||||
<Settings className="w-4 h-4 mr-2" />
|
||||
Run Setup Wizard
|
||||
</Button>
|
||||
|
||||
{apiKeys.anthropic && (
|
||||
<Button
|
||||
onClick={deleteAnthropicKey}
|
||||
|
||||
Reference in New Issue
Block a user