| 17 | const returnTo = searchParams.get('returnTo'); |
| 18 | |
| 19 | const handleSubmit = async (e: React.FormEvent) => { |
| 20 | e.preventDefault(); |
| 21 | setError(null); |
| 22 | |
| 23 | const trimmedUsername = username.trim(); |
| 24 | const trimmedPassword = password.trim(); |
| 25 | |
| 26 | // Validate inputs |
| 27 | if (!trimmedUsername || !trimmedPassword) { |
| 28 | setError('Please enter both username and password'); |
| 29 | return; |
| 30 | } |
| 31 | |
| 32 | setIsLoading(true); |
| 33 | |
| 34 | try { |
| 35 | // Validate credentials and set session cookie via /auth/login endpoint |
| 36 | // This sets an httpOnly cookie for browser navigation to protected routes (e.g., /analytics/) |
| 37 | // Use relative path to ensure cookie is set via nginx (same origin as /analytics/* routes) |
| 38 | const credentials = btoa(`${trimmedUsername}:${trimmedPassword}`); |
| 39 | const loginResponse = await fetch('/api/v1/auth/login', { |
| 40 | method: 'POST', |
| 41 | headers: { |
| 42 | Authorization: `Basic ${credentials}`, |
| 43 | 'Content-Type': 'application/json', |
| 44 | }, |
| 45 | credentials: 'include', // Important: include cookies in the response |
| 46 | }); |
| 47 | |
| 48 | if (!loginResponse.ok) { |
| 49 | if (loginResponse.status === 401) { |
| 50 | throw new Error('Invalid username or password'); |
| 51 | } |
| 52 | throw new Error( |
| 53 | `Authentication failed: ${loginResponse.status} ${loginResponse.statusText}`, |
| 54 | ); |
| 55 | } |
| 56 | |
| 57 | // Store credentials for API requests (Basic auth header) |
| 58 | setAdminCredentials(trimmedUsername, trimmedPassword); |
| 59 | |
| 60 | // Success - redirect to returnTo URL or home |
| 61 | if (returnTo) { |
| 62 | // For paths like /analytics/*, use full page navigation since they're served by nginx |
| 63 | if (returnTo.startsWith('/analytics')) { |
| 64 | window.location.href = returnTo; |
| 65 | } else { |
| 66 | navigate(returnTo); |
| 67 | } |
| 68 | } else { |
| 69 | navigate('/'); |
| 70 | } |
| 71 | } catch (err) { |
| 72 | // Clear credentials on error |
| 73 | useAuthStore.getState().clear(); |
| 74 | setError(err instanceof Error ? err.message : 'Login failed'); |
| 75 | } finally { |
| 76 | setIsLoading(false); |