import { useMemo, useState } from 'react'; import type { FormEvent } from 'react'; import { Link, Navigate, useNavigate, useSearchParams } from 'react-router-dom'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { AlertCircle, ArrowRight, Github, Loader2, Mail, User2 } from 'lucide-react'; import { AuthError, getAuthBootstrap, startGoogleSignIn, signInWithEmail, signUpWithEmail, startGitHubSignIn, } from '@/lib/auth-client'; import { useAuthSession } from '@/lib/use-auth-session'; function sanitizeRedirect(raw: string | null): string { if (!raw) { return '/projects'; } const value = raw.trim(); if (!value.startsWith('/') || value.startsWith('//')) { return '/projects'; } return value; } function buildOAuthCallbackURL(path: string): string { const cleanPath = sanitizeRedirect(path); return `${window.location.origin}${cleanPath}`; } function GoogleMark() { return ( ); } function AuthCard({ title, subtitle, children, }: { title: string; subtitle: string; children: React.ReactNode; }) { return (

{title}

{subtitle}

{children}
); } function AuthLayout({ children }: { children: React.ReactNode }) { return (
Containr Containr Self-hosted container platform
{children}

Sessions are cookie-based ยท first account becomes platform owner

); } function AuthErrorNotice({ message }: { message: string }) { return (
{message}
); } function AuthInfoNotice({ message }: { message: string }) { return (
{message}
); } export function SignInPage() { const navigate = useNavigate(); const queryClient = useQueryClient(); const [searchParams] = useSearchParams(); const redirectPath = useMemo(() => sanitizeRedirect(searchParams.get('redirect')), [searchParams]); const sessionQuery = useAuthSession(); const bootstrapQuery = useQuery({ queryKey: ['auth-bootstrap'], queryFn: getAuthBootstrap, }); const providers = bootstrapQuery.data?.providers ?? []; const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(null); const [info, setInfo] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); if (sessionQuery.data) { return ; } if (bootstrapQuery.data?.mode === 'register') { return ; } const submitEmailPassword = async (event: FormEvent) => { event.preventDefault(); setError(null); setInfo(null); setIsSubmitting(true); try { await signInWithEmail(email.trim(), password); await queryClient.invalidateQueries({ queryKey: ['auth-session'] }); navigate(redirectPath, { replace: true }); } catch (exception) { const message = exception instanceof AuthError ? exception.message : 'Failed to sign in'; setError(message); } finally { setIsSubmitting(false); } }; const signInWithGitHubProvider = async () => { setError(null); try { await startGitHubSignIn(buildOAuthCallbackURL(redirectPath)); } catch (exception) { const message = exception instanceof AuthError ? exception.message : 'GitHub sign-in failed'; setError(message); } }; const signInWithGoogleProvider = async () => { setError(null); try { await startGoogleSignIn(buildOAuthCallbackURL(redirectPath)); } catch (exception) { const message = exception instanceof AuthError ? exception.message : 'Google sign-in failed'; setError(message); } }; return ( 0 ? 'Use email/password or a connected provider.' : 'Use your email and password.'} > {error ? : null} {info ? : null} {bootstrapQuery.isLoading ? : null} {bootstrapQuery.isError ? : null}
{providers.length > 0 ? ( <>
or continue with
{providers.includes('github') ? ( ) : null} {providers.includes('google') ? ( ) : null}
) : null}
Need access? Ask platform owner to create account.
); } export function SignUpPage() { const navigate = useNavigate(); const queryClient = useQueryClient(); const [searchParams] = useSearchParams(); const redirectPath = useMemo(() => sanitizeRedirect(searchParams.get('redirect')), [searchParams]); const sessionQuery = useAuthSession(); const bootstrapQuery = useQuery({ queryKey: ['auth-bootstrap'], queryFn: getAuthBootstrap, }); const [name, setName] = useState(''); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); if (sessionQuery.data) { return ; } // Registration stays available when the owner reopened it in Settings. if (bootstrapQuery.data?.mode === 'login' && !bootstrapQuery.data.signupEnabled) { return ; } const submitSignUp = async (event: FormEvent) => { event.preventDefault(); setError(null); setIsSubmitting(true); try { await signUpWithEmail(name.trim(), email.trim(), password); await queryClient.invalidateQueries({ queryKey: ['auth-session'] }); navigate(redirectPath, { replace: true }); } catch (exception) { const message = exception instanceof AuthError ? exception.message : 'Failed to create account'; setError(message); } finally { setIsSubmitting(false); } }; return ( {error ? : null} {bootstrapQuery.isLoading ? : null} {bootstrapQuery.isError ? : null}
Already signed up? Go to sign in
); }