"use client" import {useMemo, useState} from "react" import {useMutation, useQuery} from "@tanstack/react-query" import {useRouter, useSearchParams} from "next/navigation" import {KeyRound, ShieldCheck, UserPlus} from "lucide-react" import {toast} from "sonner" import {useAuth} from "@/components/providers/auth-provider" import {Button} from "@/components/ui/button" import {Input} from "@/components/ui/input" import {Separator} from "@/components/ui/separator" import {Spinner} from "@/components/ui/spinner" import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs" import {Card, CardContent} from "@/components/ui/card" import services from "@/lib/services" function getRedirectTarget(searchParams: ReturnType) { const callbackUrl = searchParams.get("callbackUrl") const storedRedirect = sessionStorage.getItem("redirect_after_login") const target = callbackUrl || storedRedirect || "/home" if (storedRedirect) { sessionStorage.removeItem("redirect_after_login") } return target } function persistRedirectTarget(searchParams: ReturnType) { const callbackUrl = searchParams.get("callbackUrl") if (callbackUrl) { sessionStorage.setItem("redirect_after_login", callbackUrl) } } export function LoginForm() { const router = useRouter() const searchParams = useSearchParams() const { setUser } = useAuth() const [mode, setMode] = useState<"login" | "register">("login") const [username, setUsername] = useState("") const [password, setPassword] = useState("") const [nickname, setNickname] = useState("") const [errorMessage, setErrorMessage] = useState("") const publicConfigQuery = useQuery({ queryKey: ["public-config"], queryFn: services.config.getPublicConfig, }) const authSourcesQuery = useQuery({ queryKey: ["auth-sources"], queryFn: services.auth.getAuthSources, enabled: publicConfigQuery.data?.oidc_login_enabled ?? true, }) const redirectTarget = useMemo( () => getRedirectTarget(searchParams), [searchParams], ) const loginMutation = useMutation({ mutationFn: services.auth.login, onSuccess: (user) => { setUser(user) router.replace(redirectTarget) }, onError: (error: Error) => { setErrorMessage(error.message || "登录失败,请重试") }, }) const registerMutation = useMutation({ mutationFn: services.auth.register, onSuccess: (user) => { setUser(user) router.replace(redirectTarget) }, onError: (error: Error) => { setErrorMessage(error.message || "注册失败,请重试") }, }) const handlePasswordLogin = () => { setErrorMessage("") loginMutation.mutate({ username: username.trim(), password, }) } const handleRegister = () => { setErrorMessage("") registerMutation.mutate({ username: username.trim(), password, nickname: nickname.trim() || undefined, }) } const handleOAuthLogin = async (sourceName: string) => { try { setErrorMessage("") persistRedirectTarget(searchParams) const { authorize_url } = await services.auth.getAuthorizeUrl(sourceName) window.location.href = authorize_url } catch (error) { toast.error(error instanceof Error ? error.message : "第三方登录失败") } } const registrationEnabled = (publicConfigQuery.data?.registration_enabled ?? true) && (publicConfigQuery.data?.password_register_enabled ?? true) const passwordLoginEnabled = publicConfigQuery.data?.password_login_enabled ?? true const authSources = authSourcesQuery.data ?? [] return (

账号登录

使用账号密码或第三方 OIDC 认证源登录

setMode(value as "login" | "register")}> 登录 注册
setUsername(e.target.value)} placeholder="用户名" autoComplete="username" /> setPassword(e.target.value)} type="password" placeholder="密码" autoComplete="current-password" />
{errorMessage ? (
{errorMessage}
) : null}
setUsername(e.target.value)} placeholder="用户名" autoComplete="username" /> setNickname(e.target.value)} placeholder="昵称(可选)" autoComplete="nickname" /> setPassword(e.target.value)} type="password" placeholder="密码(至少 8 位)" autoComplete="new-password" />
{errorMessage ? (
{errorMessage}
) : null}
第三方认证源
{authSources.length > 0 ? ( authSources.map((source) => ( )) ) : (
暂无可用认证源
)}
) }