Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | 7x 7x 20x 20x 20x 59x 59x 20x 20x 20x 20x 15x 3x 1x 1x 1x 1x 1x 1x 20x | import { useState, useEffect } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { Zap, Loader2 } from 'lucide-react'
import { useAuthStore } from './authStore'
import { getEnv } from '@/lib/env'
import { Button } from '@/components/ui/button'
export function LoginPage() {
const navigate = useNavigate()
const [searchParams] = useSearchParams()
const isAuthenticated = useAuthStore((s) => s.isAuthenticated)
const setAuth = useAuthStore((s) => s.setAuth)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const expired = searchParams.get('expired') === 'true'
useEffect(() => {
if (isAuthenticated) {
navigate('/', { replace: true })
}
}, [isAuthenticated, navigate])
async function handleLogin() {
setLoading(true)
setError(null)
if (import.meta.env.DEV) {
await new Promise((r) => setTimeout(r, 600))
setAuth('mock-token', {
sub: 'user-1',
preferred_username: 'admin',
email: 'admin@keskikuja.site',
groups: ['admin'],
})
navigate('/', { replace: true })
} else {
tryE {
const { OIDC_AUTHORITY, OIDC_CLIENT_ID, DOMAIN } = getEnv()
const state = crypto.randomUUID()
sessionStorage.setItem('oidc-state', state)
const redirectUri = `https://${DOMAIN}/%23/callback`
const params = new URLSearchParams({
client_id: OIDC_CLIENT_ID,
redirect_uri: redirectUri,
response_type: 'token',
scope: 'openid profile email groups',
state,
})
window.location.href = `${OIDC_AUTHORITY}/api/oidc/authorization?${params}`
} catch {
setError('Failed to connect to authentication server. Try again.')
setLoading(false)
}
}
}
return (
<div className="flex min-h-screen flex-col items-center justify-center">
<div className="flex flex-col items-center gap-6">
<Zap className="h-16 w-16 text-primary" />
<h1 className="text-4xl font-bold">Agent Platform</h1>
{expired && (
<p className="text-sm text-destructive">Your session has expired. Please log in again.</p>
)}
{error && (
<p className="text-sm text-destructive">{error}</p>
)}
</div>
<div className="mt-4 max-w-md text-center text-sm text-muted-foreground leading-relaxed">
<p>
The industrial
revolution wasn't the steam engine. It was the{' '}
<em>factory system</em> — steam engine + standardized processes +
division of labor + repeatability.
</p>
<p className="mt-3">
AI is the steam engine.{' '}
<strong className="font-semibold text-foreground">
Agent Platform is the factory.
</strong>
</p>
<p className="mt-3">
Without a platform, AI is a single tool that every engineer uses
however they see fit. With a platform, AI is an{' '}
<em>industrial process</em>: versioning, repeatability, quality
control, scaling.
</p>
<p className="mt-3 italic text-foreground/60">
This project is an answer to the question: how do we move from AI
craftsmanship to AI industry?
</p>
</div>
<Button onClick={handleLogin} disabled={loading} className="mt-8">
{loading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Redirecting...
</>
) : (
'Login with OIDC'
)}
</Button>
<p className="mt-4 text-muted-foreground">
Sign in to manage your pipeline runs
</p>
<p className="fixed bottom-4 text-sm text-muted-foreground">v0.1.0</p>
</div>
)
}
|