"use client"; import { 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 { API_BASE_URL } 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 { 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 { const router = useRouter(); const searchParams = useSearchParams(); 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(() => { 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); signIn.oauth2({ providerId, callbackURL: "/" }).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); }, []); return (

Welcome to Mosaic Stack

Your personal assistant platform. Organize tasks, events, and projects with a PDA-friendly approach.

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