"use client" import Image from "next/image" import Link from "next/link" import { useRouter, useSearchParams } from "next/navigation" import { startTransition, useEffect, useState } from "react" import { toast } from "sonner" import { useAuth } from "@/components/auth-provider" import { loginWithPassword } from "@/lib/api/auth" import { fetchPublicConfig, type PublicConfig } from "@/lib/api/config" import { useI18n } from "@/i18n/provider" import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" import { Card, CardContent } from "@/components/ui/card" import { Field, FieldDescription, FieldGroup, FieldLabel, FieldSeparator, } from "@/components/ui/field" import { Input } from "@/components/ui/input" import { KeyRoundIcon, Loader2Icon, TriangleAlertIcon } from "lucide-react" export function LoginForm({ className, ...props }: React.ComponentProps<"div">) { const t = useI18n() const router = useRouter() const searchParams = useSearchParams() const { session, ready } = useAuth() const [isPending, setIsPending] = useState(false) const [publicConfig, setPublicConfig] = useState(null) const [publicConfigError, setPublicConfigError] = useState(null) const nextPath = searchParams.get("next") const oidcError = searchParams.get("oidcError") const redirectPath = nextPath && nextPath.startsWith("/") ? nextPath : "/dashboard" const enabledProviderCount = Number(publicConfig?.oidcEnabled) const isPasswordLoginEnabled = publicConfig?.passwordLoginEnabled !== false // Redirect-only mode: when OIDC is the only login transport, skip the // chooser and go straight to the provider. Suppressed while the session // probe is in flight (an already-signed-in visitor goes to their // destination instead of the IdP), and when // the provider bounced back with ?oidcError= - otherwise this would loop. const shouldRedirectToOIDC = Boolean( ready && !session && publicConfig && publicConfig.oidcEnabled && !publicConfig.passwordLoginEnabled && !oidcError, ) useEffect(() => { if (!shouldRedirectToOIDC) { return } window.location.href = `/api/auth/oidc_login?next=${encodeURIComponent(redirectPath)}` }, [shouldRedirectToOIDC, redirectPath]) useEffect(() => { if (session) { router.replace(redirectPath) } }, [redirectPath, router, session]) useEffect(() => { if (oidcError) { toast.error(oidcError) } }, [oidcError]) useEffect(() => { let cancelled = false void fetchPublicConfig() .then((options) => { if (!cancelled) { setPublicConfig(options) setPublicConfigError(null) } }) .catch((error) => { if (!cancelled) { setPublicConfig(null) setPublicConfigError(error instanceof Error ? error.message : "") } }) return () => { cancelled = true } }, []) async function handleSubmit(event: React.FormEvent) { event.preventDefault() if (!isPasswordLoginEnabled) { return } const formData = new FormData(event.currentTarget) const username = formData.get("username")?.toString().trim() ?? "" const password = formData.get("password")?.toString() ?? "" setIsPending(true) try { await loginWithPassword({ username, password }) toast.success(t("auth.loginSuccess")) startTransition(() => { router.push(redirectPath) }) } catch (error) { toast.error(error instanceof Error ? error.message : t("auth.loginFailed")) } finally { setIsPending(false) } } if (publicConfigError) { return (

{t("auth.optionsLoadFailed")}

{publicConfigError || t("api.requestFailed")}

) } if (!publicConfig) { return (

{t("auth.loadingOptions")}

) } if (shouldRedirectToOIDC) { return (

{t("auth.redirectingToOidc")}

) } return (

{t("auth.welcome")}

{t("auth.loginDescription", { brand: publicConfig.companyName || t("app.brand") })}

{isPasswordLoginEnabled ? ( <> {t("auth.username")}
{t("auth.password")} {t("auth.forgotPassword")}
) : null} {enabledProviderCount > 0 ? ( <> {isPasswordLoginEnabled ? ( {t("auth.continueWith")} ) : null} {publicConfig.oidcEnabled ? ( ) : null} ) : null} {/* {t("auth.noAccount")} {t("auth.signUp")} */}
{/* shadcn login-04 uses a plain img for the decorative side panel. */} {/* eslint-disable-next-line @next/next/no-img-element */} {t("auth.imageAlt")}
{t("auth.termsPrefix")}{" "} {t("auth.termsOfService")} {" "} {t("auth.termsJoiner")}{" "} {t("auth.privacyPolicy")} .
) }