"use client"; import { Suspense, useEffect, useState, useCallback } from "react"; import type { ReactElement } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import { Loader2 } from "lucide-react"; import type { AuthConfigResponse, AuthProviderConfig } from "@mosaic/shared"; import { AuthShell, AuthCard, AuthBrand, AuthStatusPill } from "@mosaic/ui"; import { API_BASE_URL, IS_MOCK_AUTH_MODE } from "@/lib/config"; import { signIn } from "@/lib/auth-client"; import { fetchWithRetry } from "@/lib/auth/fetch-with-retry"; import { parseAuthError } from "@/lib/auth/auth-errors"; import { useAuth } from "@/lib/auth/auth-context"; import { OAuthButton } from "@/components/auth/OAuthButton"; import { LoginForm } from "@/components/auth/LoginForm"; import { AuthDivider } from "@/components/auth/AuthDivider"; import { AuthErrorBanner } from "@/components/auth/AuthErrorBanner"; export default function LoginPage(): ReactElement { return (
} >
); } function LoginPageContent(): ReactElement { const router = useRouter(); const searchParams = useSearchParams(); const { isAuthenticated, refreshSession } = useAuth(); const [config, setConfig] = useState(undefined); const [loadingConfig, setLoadingConfig] = useState(true); const [retryCount, setRetryCount] = useState(0); const [oauthLoading, setOauthLoading] = useState(null); const [credentialsLoading, setCredentialsLoading] = useState(false); const [error, setError] = useState(null); const [urlError, setUrlError] = useState(null); /* Read ?error= query param on mount and map to PDA-friendly message */ useEffect(() => { const errorCode = searchParams.get("error"); if (errorCode) { const parsed = parseAuthError(errorCode); setUrlError(parsed.message); // Clean up the URL by removing the error param without triggering navigation const nextParams = new URLSearchParams(searchParams.toString()); nextParams.delete("error"); const query = nextParams.toString(); router.replace(query ? `/login?${query}` : "/login"); } }, [searchParams, router]); useEffect(() => { if (IS_MOCK_AUTH_MODE && isAuthenticated) { router.replace("/tasks"); } }, [isAuthenticated, router]); useEffect(() => { if (IS_MOCK_AUTH_MODE) { setConfig({ providers: [] }); setLoadingConfig(false); return; } let cancelled = false; async function fetchConfig(): Promise { try { const response = await fetchWithRetry(`${API_BASE_URL}/auth/config`); if (!response.ok) { throw new Error("Failed to fetch auth config"); } const data = (await response.json()) as AuthConfigResponse; if (!cancelled) { setConfig(data); } } catch (err: unknown) { if (!cancelled) { console.error("[Auth] Failed to load auth config:", err); setConfig(null); setUrlError( "Unable to load sign-in options. Please refresh the page or try again in a moment." ); } } finally { if (!cancelled) { setLoadingConfig(false); } } } void fetchConfig(); return (): void => { cancelled = true; }; }, [retryCount]); const oauthProviders: AuthProviderConfig[] = config?.providers.filter((p) => p.type === "oauth") ?? []; const credentialProviders: AuthProviderConfig[] = config?.providers.filter((p) => p.type === "credentials") ?? []; const hasOAuth = oauthProviders.length > 0; const hasCredentials = credentialProviders.length > 0; const handleOAuthLogin = useCallback((providerId: string): void => { setOauthLoading(providerId); setError(null); const callbackURL = typeof window !== "undefined" ? new URL("/", window.location.origin).toString() : "/"; signIn .oauth2({ providerId, callbackURL }) .then((result) => { // BetterAuth returns Data | Error union — check for error or missing redirect URL const hasError = "error" in result && result.error; const hasUrl = "data" in result && result.data?.url; if (hasError || !hasUrl) { const errObj = hasError ? result.error : null; const message = errObj && typeof errObj === "object" && "message" in errObj ? String(errObj.message) : "no redirect URL"; console.error(`[Auth] OAuth sign-in failed for ${providerId}:`, message); setError("Unable to connect to the sign-in provider. Please try again in a moment."); setOauthLoading(null); } // If data.url exists, BetterAuth's client will redirect the browser automatically. // No need to reset loading — the page is navigating away. }) .catch((err: unknown) => { const message = err instanceof Error ? err.message : String(err); console.error(`[Auth] OAuth sign-in initiation failed for ${providerId}:`, message); setError("Unable to connect to the sign-in provider. Please try again in a moment."); setOauthLoading(null); }); }, []); const handleCredentialsLogin = useCallback( async (email: string, password: string): Promise => { setCredentialsLoading(true); setError(null); try { const result = await signIn.email({ email, password }); if (result.error) { const parsed = parseAuthError( result.error.message ? new Error(result.error.message) : result.error ); setError(parsed.message); } else { router.push("/tasks"); } } catch (err: unknown) { const parsed = parseAuthError(err); console.error("[Auth] Credentials sign-in failed:", err); setError(parsed.message); } finally { setCredentialsLoading(false); } }, [router] ); const handleRetry = useCallback((): void => { setConfig(undefined); setLoadingConfig(true); setUrlError(null); setError(null); setRetryCount((c) => c + 1); }, []); const handleMockLogin = useCallback(async (): Promise => { setError(null); try { await refreshSession(); router.push("/tasks"); } catch (err: unknown) { const parsed = parseAuthError(err); setError(parsed.message); } }, [refreshSession, router]); if (IS_MOCK_AUTH_MODE) { return (

Command Center

Local mock auth mode is active

{error && }
); } return (

Command Center

Sign in to your orchestration platform

{loadingConfig ? (
) : config === null ? (
) : (
{urlError && (
{ setUrlError(null); }} />
)} {error && !hasCredentials && (
{ setError(null); }} />
)} {hasCredentials && ( )} {hasOAuth && hasCredentials && } {hasOAuth && (
{oauthProviders.map((provider) => ( { handleOAuthLogin(provider.id); }} isLoading={oauthLoading === provider.id} disabled={oauthLoading !== null && oauthLoading !== provider.id} /> ))}
)}
)}
); }