All files / features / auth LoginPage.tsx

68.96% Statements 20/29
80% Branches 8/10
100% Functions 6/6
65.38% Lines 17/26

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 23x 23x 7x 7x 7x   7x 6x 1x         1x 1x 1x 1x 1x           1x                                           7x                                                                                                          
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 E{
      try {
        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>
  )
}